|
|
@ -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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QVariantHash torrents; |
|
|
|
|
|
|
|
QHash<QString, QStringList> trackers; |
|
|
|
|
|
|
|
for (const BitTorrent::Torrent *torrent : asConst(session->torrents())) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
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); |
|
|
|
const int acceptedID = params()[u"rid"_qs].toInt(); |
|
|
|
map.remove(KEY_TORRENT_ID); |
|
|
|
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).
|
|
|
|
if (m_maindataAcceptedID == acceptedID) |
|
|
|
// 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(); |
|
|
|
// We are still able to send changes for the current state of the data having by client.
|
|
|
|
const auto iterID = lastResponseTorrents.find(torrentID.toString()); |
|
|
|
fullUpdate = false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (iterID != lastResponseTorrents.end()) |
|
|
|
const int id = (m_maindataLastSentID % 1000000) + 1; // cycle between 1 and 1000000
|
|
|
|
{ |
|
|
|
setResult(generateMaindataSyncData(id, fullUpdate)); |
|
|
|
const QVariantMap torrentData = iterID->toMap(); |
|
|
|
m_maindataLastSentID = id; |
|
|
|
const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (iterLastActivity != torrentData.end()) |
|
|
|
void SyncController::makeMaindataSnapshot() |
|
|
|
{ |
|
|
|
{ |
|
|
|
const int lastValue = iterLastActivity->toInt(); |
|
|
|
m_knownTrackers.clear(); |
|
|
|
if (qAbs(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toInt()) < 15) |
|
|
|
m_maindataAcceptedID = 0; |
|
|
|
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue; |
|
|
|
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())) |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
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 (const QString &categoryName : asConst(m_updatedCategories)) |
|
|
|
for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
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(); |
|
|
|
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 ¤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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|