Browse Source

Improve sync API performance

PR #18394.
adaptive-webui-19844
Vladimir Golovnev 2 years ago committed by GitHub
parent
commit
d06f78dbbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/base/bittorrent/session.h
  2. 1
      src/base/bittorrent/sessionimpl.cpp
  3. 498
      src/webui/api/synccontroller.cpp
  4. 63
      src/webui/api/synccontroller.h

1
src/base/bittorrent/session.h

@ -433,6 +433,7 @@ namespace BitTorrent @@ -433,6 +433,7 @@ namespace BitTorrent
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void categoryOptionsChanged(const QString &categoryName);
void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url);
void fullDiskError(Torrent *torrent, const QString &msg);

1
src/base/bittorrent/sessionimpl.cpp

@ -865,6 +865,7 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio @@ -865,6 +865,7 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
}
}
emit categoryOptionsChanged(name);
return true;
}

498
src/webui/api/synccontroller.cpp

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,10 +30,12 @@ @@ -30,10 +30,12 @@
#include <algorithm>
#include <QJsonArray>
#include <QJsonObject>
#include <QMetaObject>
#include <QThreadPool>
#include "base/algorithm.h"
#include "base/bittorrent/cachestatus.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/peeraddress.h"
@ -106,14 +108,24 @@ namespace @@ -106,14 +108,24 @@ namespace
const QString KEY_TRANSFER_TOTAL_WASTE_SESSION = u"total_wasted_session"_qs;
const QString KEY_TRANSFER_WRITE_CACHE_OVERLOAD = u"write_cache_overload"_qs;
const QString KEY_SUFFIX_REMOVED = u"_removed"_qs;
const QString KEY_CATEGORIES = u"categories"_qs;
const QString KEY_CATEGORIES_REMOVED = KEY_CATEGORIES + KEY_SUFFIX_REMOVED;
const QString KEY_TAGS = u"tags"_qs;
const QString KEY_TAGS_REMOVED = KEY_TAGS + KEY_SUFFIX_REMOVED;
const QString KEY_TORRENTS = u"torrents"_qs;
const QString KEY_TORRENTS_REMOVED = KEY_TORRENTS + KEY_SUFFIX_REMOVED;
const QString KEY_TRACKERS = u"trackers"_qs;
const QString KEY_TRACKERS_REMOVED = KEY_TRACKERS + KEY_SUFFIX_REMOVED;
const QString KEY_SERVER_STATE = u"server_state"_qs;
const QString KEY_FULL_UPDATE = u"full_update"_qs;
const QString KEY_RESPONSE_ID = u"rid"_qs;
const QString KEY_SUFFIX_REMOVED = u"_removed"_qs;
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData);
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems);
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems);
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
QVariantMap getTransferInfo()
{
@ -332,23 +344,19 @@ namespace @@ -332,23 +344,19 @@ namespace
}
}
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
QJsonObject generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
{
QVariantMap syncData;
bool fullUpdate = true;
int lastResponseId = 0;
if (acceptedResponseId > 0)
const int lastResponseId = (acceptedResponseId > 0) ? lastData[KEY_RESPONSE_ID].toInt() : 0;
if (lastResponseId > 0)
{
lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
if (lastResponseId == acceptedResponseId)
lastAcceptedData = lastData;
int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt();
if (lastAcceptedResponseId == acceptedResponseId)
if (const int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt()
; lastAcceptedResponseId == acceptedResponseId)
{
processMap(lastAcceptedData, data, syncData);
fullUpdate = false;
}
}
@ -359,13 +367,17 @@ namespace @@ -359,13 +367,17 @@ namespace
syncData = data;
syncData[KEY_FULL_UPDATE] = true;
}
else
{
processMap(lastAcceptedData, data, syncData);
}
lastResponseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000
const int responseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000
lastData = data;
lastData[KEY_RESPONSE_ID] = lastResponseId;
syncData[KEY_RESPONSE_ID] = lastResponseId;
lastData[KEY_RESPONSE_ID] = responseId;
syncData[KEY_RESPONSE_ID] = responseId;
return syncData;
return QJsonObject::fromVariantMap(syncData);
}
}
@ -441,49 +453,74 @@ SyncController::SyncController(IApplication *app, QObject *parent) @@ -441,49 +453,74 @@ SyncController::SyncController(IApplication *app, QObject *parent)
// - rid (int): last response id
void SyncController::maindataAction()
{
const auto *session = BitTorrent::Session::instance();
QVariantMap data;
QVariantHash torrents;
QHash<QString, QStringList> trackers;
for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
if (m_maindataAcceptedID < 0)
{
const BitTorrent::TorrentID torrentID = torrent->id();
makeMaindataSnapshot();
const auto *btSession = BitTorrent::Session::instance();
connect(btSession, &BitTorrent::Session::categoryAdded, this, &SyncController::onCategoryAdded);
connect(btSession, &BitTorrent::Session::categoryRemoved, this, &SyncController::onCategoryRemoved);
connect(btSession, &BitTorrent::Session::categoryOptionsChanged, this, &SyncController::onCategoryOptionsChanged);
connect(btSession, &BitTorrent::Session::subcategoriesSupportChanged, this, &SyncController::onSubcategoriesSupportChanged);
connect(btSession, &BitTorrent::Session::tagAdded, this, &SyncController::onTagAdded);
connect(btSession, &BitTorrent::Session::tagRemoved, this, &SyncController::onTagRemoved);
connect(btSession, &BitTorrent::Session::torrentAdded, this, &SyncController::onTorrentAdded);
connect(btSession, &BitTorrent::Session::torrentAboutToBeRemoved, this, &SyncController::onTorrentAboutToBeRemoved);
connect(btSession, &BitTorrent::Session::torrentCategoryChanged, this, &SyncController::onTorrentCategoryChanged);
connect(btSession, &BitTorrent::Session::torrentMetadataReceived, this, &SyncController::onTorrentMetadataReceived);
connect(btSession, &BitTorrent::Session::torrentPaused, this, &SyncController::onTorrentPaused);
connect(btSession, &BitTorrent::Session::torrentResumed, this, &SyncController::onTorrentResumed);
connect(btSession, &BitTorrent::Session::torrentSavePathChanged, this, &SyncController::onTorrentSavePathChanged);
connect(btSession, &BitTorrent::Session::torrentSavingModeChanged, this, &SyncController::onTorrentSavingModeChanged);
connect(btSession, &BitTorrent::Session::torrentTagAdded, this, &SyncController::onTorrentTagAdded);
connect(btSession, &BitTorrent::Session::torrentTagRemoved, this, &SyncController::onTorrentTagRemoved);
connect(btSession, &BitTorrent::Session::torrentsUpdated, this, &SyncController::onTorrentsUpdated);
connect(btSession, &BitTorrent::Session::trackersChanged, this, &SyncController::onTorrentTrackersChanged);
}
QVariantMap map = serialize(*torrent);
map.remove(KEY_TORRENT_ID);
const int acceptedID = params()[u"rid"_qs].toInt();
bool fullUpdate = true;
if ((acceptedID > 0) && (m_maindataLastSentID > 0))
{
if (m_maindataLastSentID == acceptedID)
{
m_maindataAcceptedID = acceptedID;
m_maindataSyncBuf = {};
}
// Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue).
// So we don't need unnecessary updates of last activity time in response.
const auto iterTorrents = m_lastMaindataResponse.find(u"torrents"_qs);
if (iterTorrents != m_lastMaindataResponse.end())
if (m_maindataAcceptedID == acceptedID)
{
const QVariantHash lastResponseTorrents = iterTorrents->toHash();
const auto iterID = lastResponseTorrents.find(torrentID.toString());
// We are still able to send changes for the current state of the data having by client.
fullUpdate = false;
}
}
if (iterID != lastResponseTorrents.end())
{
const QVariantMap torrentData = iterID->toMap();
const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME);
const int id = (m_maindataLastSentID % 1000000) + 1; // cycle between 1 and 1000000
setResult(generateMaindataSyncData(id, fullUpdate));
m_maindataLastSentID = id;
}
if (iterLastActivity != torrentData.end())
{
const int lastValue = iterLastActivity->toInt();
if (qAbs(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toInt()) < 15)
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue;
}
}
}
void SyncController::makeMaindataSnapshot()
{
m_knownTrackers.clear();
m_maindataAcceptedID = 0;
m_maindataSnapshot = {};
const auto *session = BitTorrent::Session::instance();
for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
{
const BitTorrent::TorrentID torrentID = torrent->id();
QVariantMap serializedTorrent = serialize(*torrent);
serializedTorrent.remove(KEY_TORRENT_ID);
for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers()))
trackers[tracker.url] << torrentID.toString();
m_knownTrackers[tracker.url].insert(torrentID);
torrents[torrentID.toString()] = map;
m_maindataSnapshot.torrents[torrentID.toString()] = serializedTorrent;
}
data[u"torrents"_qs] = torrents;
QVariantHash categories;
const QStringList categoriesList = session->categories();
for (const auto &categoryName : categoriesList)
{
@ -492,31 +529,184 @@ void SyncController::maindataAction() @@ -492,31 +529,184 @@ void SyncController::maindataAction()
// adjust it to be compatible with existing WebAPI
category[u"savePath"_qs] = category.take(u"save_path"_qs);
category.insert(u"name"_qs, categoryName);
categories[categoryName] = category.toVariantMap();
m_maindataSnapshot.categories[categoryName] = category.toVariantMap();
}
data[u"categories"_qs] = categories;
QVariantList tags;
for (const QString &tag : asConst(session->tags()))
tags << tag;
data[u"tags"_qs] = tags;
m_maindataSnapshot.tags.append(tag);
for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter)
{
QStringList torrentIDs;
for (const BitTorrent::TorrentID &torrentID : asConst(trackersIter.value()))
torrentIDs.append(torrentID.toString());
m_maindataSnapshot.trackers[trackersIter.key()] = torrentIDs;
}
m_maindataSnapshot.serverState = getTransferInfo();
m_maindataSnapshot.serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
}
QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fullUpdate)
{
// if need to update existing sync data
for (const QString &category : asConst(m_updatedCategories))
m_maindataSyncBuf.removedCategories.removeOne(category);
for (const QString &category : asConst(m_removedCategories))
m_maindataSyncBuf.categories.remove(category);
for (const QString &tag : asConst(m_addedTags))
m_maindataSyncBuf.removedTags.removeOne(tag);
for (const QString &tag : asConst(m_removedTags))
m_maindataSyncBuf.tags.removeOne(tag);
for (const BitTorrent::TorrentID &torrentID : asConst(m_updatedTorrents))
m_maindataSyncBuf.removedTorrents.removeOne(torrentID.toString());
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
m_maindataSyncBuf.torrents.remove(torrentID.toString());
for (const QString &tracker : asConst(m_updatedTrackers))
m_maindataSyncBuf.removedTrackers.removeOne(tracker);
for (const QString &tracker : asConst(m_removedTrackers))
m_maindataSyncBuf.trackers.remove(tracker);
const auto *session = BitTorrent::Session::instance();
QVariantHash trackersHash;
for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i)
for (const QString &categoryName : asConst(m_updatedCategories))
{
trackersHash[i.key()] = i.value();
const BitTorrent::CategoryOptions categoryOptions = session->categoryOptions(categoryName);
auto category = categoryOptions.toJSON().toVariantMap();
// adjust it to be compatible with existing WebAPI
category[u"savePath"_qs] = category.take(u"save_path"_qs);
category.insert(u"name"_qs, categoryName);
auto &categorySnapshot = m_maindataSnapshot.categories[categoryName];
processMap(categorySnapshot, category, m_maindataSyncBuf.categories[categoryName]);
categorySnapshot = category;
}
m_updatedCategories.clear();
for (const QString &category : asConst(m_removedCategories))
{
m_maindataSyncBuf.removedCategories.append(category);
m_maindataSnapshot.categories.remove(category);
}
m_removedCategories.clear();
for (const QString &tag : asConst(m_addedTags))
{
m_maindataSyncBuf.tags.append(tag);
m_maindataSnapshot.tags.append(tag);
}
m_addedTags.clear();
for (const QString &tag : asConst(m_removedTags))
{
m_maindataSyncBuf.removedTags.append(tag);
m_maindataSnapshot.tags.removeOne(tag);
}
m_removedTags.clear();
for (const BitTorrent::TorrentID &torrentID : asConst(m_updatedTorrents))
{
const BitTorrent::Torrent *torrent = session->getTorrent(torrentID);
Q_ASSERT(torrent);
QVariantMap serializedTorrent = serialize(*torrent);
serializedTorrent.remove(KEY_TORRENT_ID);
auto &torrentSnapshot = m_maindataSnapshot.torrents[torrentID.toString()];
processMap(torrentSnapshot, serializedTorrent, m_maindataSyncBuf.torrents[torrentID.toString()]);
torrentSnapshot = serializedTorrent;
}
m_updatedTorrents.clear();
for (const BitTorrent::TorrentID &torrentID : asConst(m_removedTorrents))
{
m_maindataSyncBuf.removedTorrents.append(torrentID.toString());
m_maindataSnapshot.torrents.remove(torrentID.toString());
}
data[u"trackers"_qs] = trackersHash;
m_removedTorrents.clear();
for (const QString &tracker : asConst(m_updatedTrackers))
{
const QSet<BitTorrent::TorrentID> torrentIDs = m_knownTrackers[tracker];
QStringList serializedTorrentIDs;
serializedTorrentIDs.reserve(torrentIDs.size());
for (const BitTorrent::TorrentID &torrentID : torrentIDs)
serializedTorrentIDs.append(torrentID.toString());
m_maindataSyncBuf.trackers[tracker] = serializedTorrentIDs;
m_maindataSnapshot.trackers[tracker] = serializedTorrentIDs;
}
m_updatedTrackers.clear();
for (const QString &tracker : asConst(m_removedTrackers))
{
m_maindataSyncBuf.removedTrackers.append(tracker);
m_maindataSnapshot.trackers.remove(tracker);
}
m_removedTrackers.clear();
QVariantMap serverState = getTransferInfo();
serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
data[u"server_state"_qs] = serverState;
processMap(m_maindataSnapshot.serverState, serverState, m_maindataSyncBuf.serverState);
m_maindataSnapshot.serverState = serverState;
const int acceptedResponseId = params()[u"rid"_qs].toInt();
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, m_lastAcceptedMaindataResponse, m_lastMaindataResponse)));
QJsonObject syncData;
syncData[KEY_RESPONSE_ID] = id;
if (fullUpdate)
{
m_maindataSyncBuf = m_maindataSnapshot;
syncData[KEY_FULL_UPDATE] = true;
}
if (!m_maindataSyncBuf.categories.isEmpty())
{
QJsonObject categories;
for (auto it = m_maindataSyncBuf.categories.cbegin(); it != m_maindataSyncBuf.categories.cend(); ++it)
categories[it.key()] = QJsonObject::fromVariantMap(it.value());
syncData[KEY_CATEGORIES] = categories;
}
if (!m_maindataSyncBuf.removedCategories.isEmpty())
syncData[KEY_CATEGORIES_REMOVED] = QJsonArray::fromStringList(m_maindataSyncBuf.removedCategories);
if (!m_maindataSyncBuf.tags.isEmpty())
syncData[KEY_TAGS] = QJsonArray::fromVariantList(m_maindataSyncBuf.tags);
if (!m_maindataSyncBuf.removedTags.isEmpty())
syncData[KEY_TAGS_REMOVED] = QJsonArray::fromStringList(m_maindataSyncBuf.removedTags);
if (!m_maindataSyncBuf.torrents.isEmpty())
{
QJsonObject torrents;
for (auto it = m_maindataSyncBuf.torrents.cbegin(); it != m_maindataSyncBuf.torrents.cend(); ++it)
torrents[it.key()] = QJsonObject::fromVariantMap(it.value());
syncData[KEY_TORRENTS] = torrents;
}
if (!m_maindataSyncBuf.removedTorrents.isEmpty())
syncData[KEY_TORRENTS_REMOVED] = QJsonArray::fromStringList(m_maindataSyncBuf.removedTorrents);
if (!m_maindataSyncBuf.trackers.isEmpty())
{
QJsonObject trackers;
for (auto it = m_maindataSyncBuf.trackers.cbegin(); it != m_maindataSyncBuf.trackers.cend(); ++it)
trackers[it.key()] = QJsonArray::fromStringList(it.value());
syncData[KEY_TRACKERS] = trackers;
}
if (!m_maindataSyncBuf.removedTrackers.isEmpty())
syncData[KEY_TRACKERS_REMOVED] = QJsonArray::fromStringList(m_maindataSyncBuf.removedTrackers);
if (!m_maindataSyncBuf.serverState.isEmpty())
syncData[KEY_SERVER_STATE] = QJsonObject::fromVariantMap(m_maindataSyncBuf.serverState);
return syncData;
}
// GET param:
@ -580,7 +770,7 @@ void SyncController::torrentPeersAction() @@ -580,7 +770,7 @@ void SyncController::torrentPeersAction()
data[u"peers"_qs] = peers;
const int acceptedResponseId = params()[u"rid"_qs].toInt();
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, m_lastAcceptedPeersResponse, m_lastPeersResponse)));
setResult(generateSyncData(acceptedResponseId, data, m_lastAcceptedPeersResponse, m_lastPeersResponse));
}
qint64 SyncController::getFreeDiskSpace()
@ -610,3 +800,189 @@ void SyncController::invokeChecker() @@ -610,3 +800,189 @@ void SyncController::invokeChecker()
freeDiskSpaceChecker->check();
});
}
void SyncController::onCategoryAdded(const QString &categoryName)
{
m_removedCategories.remove(categoryName);
m_updatedCategories.insert(categoryName);
}
void SyncController::onCategoryRemoved(const QString &categoryName)
{
m_updatedCategories.remove(categoryName);
m_removedCategories.insert(categoryName);
}
void SyncController::onCategoryOptionsChanged(const QString &categoryName)
{
Q_ASSERT(!m_removedCategories.contains(categoryName));
m_updatedCategories.insert(categoryName);
}
void SyncController::onSubcategoriesSupportChanged()
{
const QStringList categoriesList = BitTorrent::Session::instance()->categories();
for (const auto &categoryName : categoriesList)
{
if (!m_maindataSnapshot.categories.contains(categoryName))
{
m_removedCategories.remove(categoryName);
m_updatedCategories.insert(categoryName);
}
}
}
void SyncController::onTagAdded(const QString &tag)
{
m_removedTags.remove(tag);
m_addedTags.insert(tag);
}
void SyncController::onTagRemoved(const QString &tag)
{
m_addedTags.remove(tag);
m_removedTags.insert(tag);
}
void SyncController::onTorrentAdded(BitTorrent::Torrent *torrent)
{
const BitTorrent::TorrentID torrentID = torrent->id();
m_removedTorrents.remove(torrentID);
m_updatedTorrents.insert(torrentID);
for (const BitTorrent::TrackerEntry &trackerEntry : asConst(torrent->trackers()))
{
m_knownTrackers[trackerEntry.url].insert(torrentID);
m_updatedTrackers.insert(trackerEntry.url);
m_removedTrackers.remove(trackerEntry.url);
}
}
void SyncController::onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent)
{
const BitTorrent::TorrentID torrentID = torrent->id();
m_updatedTorrents.remove(torrentID);
m_removedTorrents.insert(torrentID);
for (const BitTorrent::TrackerEntry &trackerEntry : asConst(torrent->trackers()))
{
auto iter = m_knownTrackers.find(trackerEntry.url);
Q_ASSERT(iter != m_knownTrackers.end());
if (Q_UNLIKELY(iter == m_knownTrackers.end()))
continue;
QSet<BitTorrent::TorrentID> &torrentIDs = iter.value();
torrentIDs.remove(torrentID);
if (torrentIDs.isEmpty())
{
m_knownTrackers.erase(iter);
m_updatedTrackers.remove(trackerEntry.url);
m_removedTrackers.insert(trackerEntry.url);
}
else
{
m_updatedTrackers.insert(trackerEntry.url);
}
}
}
void SyncController::onTorrentCategoryChanged(BitTorrent::Torrent *torrent
, [[maybe_unused]] const QString &oldCategory)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentMetadataReceived(BitTorrent::Torrent *torrent)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentPaused(BitTorrent::Torrent *torrent)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentResumed(BitTorrent::Torrent *torrent)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentSavePathChanged(BitTorrent::Torrent *torrent)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentSavingModeChanged(BitTorrent::Torrent *torrent)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentTagAdded(BitTorrent::Torrent *torrent, [[maybe_unused]] const QString &tag)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentTagRemoved(BitTorrent::Torrent *torrent, [[maybe_unused]] const QString &tag)
{
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents)
{
for (const BitTorrent::Torrent *torrent : torrents)
m_updatedTorrents.insert(torrent->id());
}
void SyncController::onTorrentTrackersChanged(BitTorrent::Torrent *torrent)
{
using namespace BitTorrent;
const QVector<TrackerEntry> currentTrackerEntries = torrent->trackers();
QSet<QString> currentTrackers;
currentTrackers.reserve(currentTrackerEntries.size());
for (const TrackerEntry &currentTrackerEntry : currentTrackerEntries)
currentTrackers.insert(currentTrackerEntry.url);
const TorrentID torrentID = torrent->id();
Algorithm::removeIf(m_knownTrackers
, [this, torrentID, currentTrackers]
(const QString &knownTracker, QSet<TorrentID> &torrentIDs)
{
if (auto idIter = torrentIDs.find(torrentID)
; (idIter != torrentIDs.end()) && !currentTrackers.contains(knownTracker))
{
torrentIDs.erase(idIter);
if (torrentIDs.isEmpty())
{
m_updatedTrackers.remove(knownTracker);
m_removedTrackers.insert(knownTracker);
return true;
}
m_updatedTrackers.insert(knownTracker);
return false;
}
if (currentTrackers.contains(knownTracker) && !torrentIDs.contains(torrentID))
{
torrentIDs.insert(torrentID);
m_updatedTrackers.insert(knownTracker);
return false;
}
return false;
});
for (const QString &currentTracker : asConst(currentTrackers))
{
if (!m_knownTrackers.contains(currentTracker))
{
m_knownTrackers.insert(currentTracker, {torrentID});
m_updatedTrackers.insert(currentTracker);
m_removedTrackers.remove(currentTracker);
}
}
}

63
src/webui/api/synccontroller.h

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,11 +30,18 @@ @@ -30,11 +30,18 @@
#include <QElapsedTimer>
#include <QVariantMap>
#include <QSet>
#include "base/bittorrent/infohash.h"
#include "apicontroller.h"
class QThread;
namespace BitTorrent
{
class Torrent;
}
class FreeDiskSpaceChecker;
class SyncController : public APIController
@ -55,12 +62,62 @@ private: @@ -55,12 +62,62 @@ private:
qint64 getFreeDiskSpace();
void invokeChecker();
void makeMaindataSnapshot();
QJsonObject generateMaindataSyncData(int id, bool fullUpdate);
void onCategoryAdded(const QString &categoryName);
void onCategoryRemoved(const QString &categoryName);
void onCategoryOptionsChanged(const QString &categoryName);
void onSubcategoriesSupportChanged();
void onTagAdded(const QString &tag);
void onTagRemoved(const QString &tag);
void onTorrentAdded(BitTorrent::Torrent *torrent);
void onTorrentAboutToBeRemoved(BitTorrent::Torrent *torrent);
void onTorrentCategoryChanged(BitTorrent::Torrent *torrent, const QString &oldCategory);
void onTorrentMetadataReceived(BitTorrent::Torrent *torrent);
void onTorrentPaused(BitTorrent::Torrent *torrent);
void onTorrentResumed(BitTorrent::Torrent *torrent);
void onTorrentSavePathChanged(BitTorrent::Torrent *torrent);
void onTorrentSavingModeChanged(BitTorrent::Torrent *torrent);
void onTorrentTagAdded(BitTorrent::Torrent *torrent, const QString &tag);
void onTorrentTagRemoved(BitTorrent::Torrent *torrent, const QString &tag);
void onTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents);
void onTorrentTrackersChanged(BitTorrent::Torrent *torrent);
qint64 m_freeDiskSpace = 0;
QElapsedTimer m_freeDiskSpaceElapsedTimer;
bool m_isFreeDiskSpaceCheckerRunning = false;
QVariantMap m_lastMaindataResponse;
QVariantMap m_lastAcceptedMaindataResponse;
QVariantMap m_lastPeersResponse;
QVariantMap m_lastAcceptedPeersResponse;
QHash<QString, QSet<BitTorrent::TorrentID>> m_knownTrackers;
QSet<QString> m_updatedCategories;
QSet<QString> m_removedCategories;
QSet<QString> m_addedTags;
QSet<QString> m_removedTags;
QSet<QString> m_updatedTrackers;
QSet<QString> m_removedTrackers;
QSet<BitTorrent::TorrentID> m_updatedTorrents;
QSet<BitTorrent::TorrentID> m_removedTorrents;
struct MaindataSyncBuf
{
QHash<QString, QVariantMap> categories;
QVariantList tags;
QHash<QString, QVariantMap> torrents;
QHash<QString, QStringList> trackers;
QVariantMap serverState;
QStringList removedCategories;
QStringList removedTags;
QStringList removedTorrents;
QStringList removedTrackers;
};
MaindataSyncBuf m_maindataSnapshot;
MaindataSyncBuf m_maindataSyncBuf;
int m_maindataLastSentID = 0;
int m_maindataAcceptedID = -1;
};

Loading…
Cancel
Save