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. 496
      src/webui/api/synccontroller.cpp
  4. 63
      src/webui/api/synccontroller.h

1
src/base/bittorrent/session.h

@ -433,6 +433,7 @@ namespace BitTorrent
void allTorrentsFinished(); void allTorrentsFinished();
void categoryAdded(const QString &categoryName); void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName); void categoryRemoved(const QString &categoryName);
void categoryOptionsChanged(const QString &categoryName);
void downloadFromUrlFailed(const QString &url, const QString &reason); void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url); void downloadFromUrlFinished(const QString &url);
void fullDiskError(Torrent *torrent, const QString &msg); 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
} }
} }
emit categoryOptionsChanged(name);
return true; return true;
} }

496
src/webui/api/synccontroller.cpp

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -30,10 +30,12 @@
#include <algorithm> #include <algorithm>
#include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QMetaObject> #include <QMetaObject>
#include <QThreadPool> #include <QThreadPool>
#include "base/algorithm.h"
#include "base/bittorrent/cachestatus.h" #include "base/bittorrent/cachestatus.h"
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/bittorrent/peeraddress.h" #include "base/bittorrent/peeraddress.h"
@ -106,14 +108,24 @@ namespace
const QString KEY_TRANSFER_TOTAL_WASTE_SESSION = u"total_wasted_session"_qs; 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_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_FULL_UPDATE = u"full_update"_qs;
const QString KEY_RESPONSE_ID = u"rid"_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 processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData);
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems); void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems);
void processList(QVariantList prevData, const QVariantList &data, QVariantList &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() QVariantMap getTransferInfo()
{ {
@ -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; QVariantMap syncData;
bool fullUpdate = true; bool fullUpdate = true;
int lastResponseId = 0; const int lastResponseId = (acceptedResponseId > 0) ? lastData[KEY_RESPONSE_ID].toInt() : 0;
if (acceptedResponseId > 0) if (lastResponseId > 0)
{ {
lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
if (lastResponseId == acceptedResponseId) if (lastResponseId == acceptedResponseId)
lastAcceptedData = lastData; lastAcceptedData = lastData;
int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt(); if (const int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt()
; lastAcceptedResponseId == acceptedResponseId)
if (lastAcceptedResponseId == acceptedResponseId)
{ {
processMap(lastAcceptedData, data, syncData);
fullUpdate = false; fullUpdate = false;
} }
} }
@ -359,13 +367,17 @@ namespace
syncData = data; syncData = data;
syncData[KEY_FULL_UPDATE] = true; 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 = data;
lastData[KEY_RESPONSE_ID] = lastResponseId; lastData[KEY_RESPONSE_ID] = responseId;
syncData[KEY_RESPONSE_ID] = lastResponseId; syncData[KEY_RESPONSE_ID] = responseId;
return syncData; return QJsonObject::fromVariantMap(syncData);
} }
} }
@ -441,49 +453,74 @@ SyncController::SyncController(IApplication *app, QObject *parent)
// - rid (int): last response id // - rid (int): last response id
void SyncController::maindataAction() void SyncController::maindataAction()
{ {
const auto *session = BitTorrent::Session::instance(); if (m_maindataAcceptedID < 0)
{
QVariantMap data; 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);
}
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 = {};
}
QVariantHash torrents; if (m_maindataAcceptedID == acceptedID)
QHash<QString, QStringList> trackers;
for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
{ {
const BitTorrent::TorrentID torrentID = torrent->id(); // We are still able to send changes for the current state of the data having by client.
fullUpdate = false;
}
}
QVariantMap map = serialize(*torrent); const int id = (m_maindataLastSentID % 1000000) + 1; // cycle between 1 and 1000000
map.remove(KEY_TORRENT_ID); setResult(generateMaindataSyncData(id, fullUpdate));
m_maindataLastSentID = id;
}
// Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue). void SyncController::makeMaindataSnapshot()
// So we don't need unnecessary updates of last activity time in response. {
const auto iterTorrents = m_lastMaindataResponse.find(u"torrents"_qs); m_knownTrackers.clear();
if (iterTorrents != m_lastMaindataResponse.end()) m_maindataAcceptedID = 0;
{ m_maindataSnapshot = {};
const QVariantHash lastResponseTorrents = iterTorrents->toHash();
const auto iterID = lastResponseTorrents.find(torrentID.toString());
if (iterID != lastResponseTorrents.end()) const auto *session = BitTorrent::Session::instance();
{
const QVariantMap torrentData = iterID->toMap();
const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME);
if (iterLastActivity != torrentData.end()) for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
{ {
const int lastValue = iterLastActivity->toInt(); const BitTorrent::TorrentID torrentID = torrent->id();
if (qAbs(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toInt()) < 15)
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue; QVariantMap serializedTorrent = serialize(*torrent);
} serializedTorrent.remove(KEY_TORRENT_ID);
}
}
for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers())) 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(); const QStringList categoriesList = session->categories();
for (const auto &categoryName : categoriesList) for (const auto &categoryName : categoriesList)
{ {
@ -492,31 +529,184 @@ void SyncController::maindataAction()
// adjust it to be compatible with existing WebAPI // adjust it to be compatible with existing WebAPI
category[u"savePath"_qs] = category.take(u"save_path"_qs); category[u"savePath"_qs] = category.take(u"save_path"_qs);
category.insert(u"name"_qs, categoryName); 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())) for (const QString &tag : asConst(session->tags()))
tags << tag; m_maindataSnapshot.tags.append(tag);
data[u"tags"_qs] = tags;
QVariantHash trackersHash; for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter)
for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i)
{ {
trackersHash[i.key()] = i.value(); QStringList torrentIDs;
for (const BitTorrent::TorrentID &torrentID : asConst(trackersIter.value()))
torrentIDs.append(torrentID.toString());
m_maindataSnapshot.trackers[trackersIter.key()] = torrentIDs;
} }
data[u"trackers"_qs] = trackersHash;
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();
for (const QString &categoryName : asConst(m_updatedCategories))
{
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());
}
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(); QVariantMap serverState = getTransferInfo();
serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace(); serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled(); serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled(); serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval(); 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(); QJsonObject syncData;
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, m_lastAcceptedMaindataResponse, m_lastMaindataResponse))); 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: // GET param:
@ -580,7 +770,7 @@ void SyncController::torrentPeersAction()
data[u"peers"_qs] = peers; data[u"peers"_qs] = peers;
const int acceptedResponseId = params()[u"rid"_qs].toInt(); 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() qint64 SyncController::getFreeDiskSpace()
@ -610,3 +800,189 @@ void SyncController::invokeChecker()
freeDiskSpaceChecker->check(); 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 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -30,11 +30,18 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QVariantMap> #include <QVariantMap>
#include <QSet>
#include "base/bittorrent/infohash.h"
#include "apicontroller.h" #include "apicontroller.h"
class QThread; class QThread;
namespace BitTorrent
{
class Torrent;
}
class FreeDiskSpaceChecker; class FreeDiskSpaceChecker;
class SyncController : public APIController class SyncController : public APIController
@ -55,12 +62,62 @@ private:
qint64 getFreeDiskSpace(); qint64 getFreeDiskSpace();
void invokeChecker(); 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; qint64 m_freeDiskSpace = 0;
QElapsedTimer m_freeDiskSpaceElapsedTimer; QElapsedTimer m_freeDiskSpaceElapsedTimer;
bool m_isFreeDiskSpaceCheckerRunning = false; bool m_isFreeDiskSpaceCheckerRunning = false;
QVariantMap m_lastMaindataResponse;
QVariantMap m_lastAcceptedMaindataResponse;
QVariantMap m_lastPeersResponse; QVariantMap m_lastPeersResponse;
QVariantMap m_lastAcceptedPeersResponse; 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