mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-22 17:26:21 +03:00
parent
5d4766edbe
commit
d06f78dbbd
4 changed files with 499 additions and 64 deletions
|
@ -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);
|
||||
|
|
|
@ -865,6 +865,7 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
|||
}
|
||||
}
|
||||
|
||||
emit categoryOptionsChanged(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@
|
|||
|
||||
#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
|
|||
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
|
|||
}
|
||||
}
|
||||
|
||||
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
|
|||
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)
|
|||
// - rid (int): last response id
|
||||
void SyncController::maindataAction()
|
||||
{
|
||||
if (m_maindataAcceptedID < 0)
|
||||
{
|
||||
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 = {};
|
||||
}
|
||||
|
||||
if (m_maindataAcceptedID == acceptedID)
|
||||
{
|
||||
// We are still able to send changes for the current state of the data having by client.
|
||||
fullUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
const int id = (m_maindataLastSentID % 1000000) + 1; // cycle between 1 and 1000000
|
||||
setResult(generateMaindataSyncData(id, fullUpdate));
|
||||
m_maindataLastSentID = id;
|
||||
}
|
||||
|
||||
void SyncController::makeMaindataSnapshot()
|
||||
{
|
||||
m_knownTrackers.clear();
|
||||
m_maindataAcceptedID = 0;
|
||||
m_maindataSnapshot = {};
|
||||
|
||||
const auto *session = BitTorrent::Session::instance();
|
||||
|
||||
QVariantMap data;
|
||||
|
||||
QVariantHash torrents;
|
||||
QHash<QString, QStringList> trackers;
|
||||
for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
|
||||
QVariantMap map = serialize(*torrent);
|
||||
map.remove(KEY_TORRENT_ID);
|
||||
|
||||
// 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())
|
||||
{
|
||||
const QVariantHash lastResponseTorrents = iterTorrents->toHash();
|
||||
const auto iterID = lastResponseTorrents.find(torrentID.toString());
|
||||
|
||||
if (iterID != lastResponseTorrents.end())
|
||||
{
|
||||
const QVariantMap torrentData = iterID->toMap();
|
||||
const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
|||
// 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);
|
||||
|
||||
QVariantHash trackersHash;
|
||||
for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i)
|
||||
for (auto trackersIter = m_knownTrackers.cbegin(); trackersIter != m_knownTrackers.cend(); ++trackersIter)
|
||||
{
|
||||
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();
|
||||
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()
|
|||
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()
|
|||
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 ¤tTrackerEntry : 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 ¤tTracker : asConst(currentTrackers))
|
||||
{
|
||||
if (!m_knownTrackers.contains(currentTracker))
|
||||
{
|
||||
m_knownTrackers.insert(currentTracker, {torrentID});
|
||||
m_updatedTrackers.insert(currentTracker);
|
||||
m_removedTrackers.remove(currentTracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@
|
|||
|
||||
#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:
|
|||
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…
Reference in a new issue