2017-10-14 16:27:21 +03:00
|
|
|
/*
|
|
|
|
* Bittorrent Client using Qt and libtorrent.
|
|
|
|
* Copyright (C) 2018 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
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
* In addition, as a special exception, the copyright holders give permission to
|
|
|
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
|
|
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
|
|
|
* and distribute the linked executables. You must obey the GNU General Public
|
|
|
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
|
|
|
* modify file(s), you may extend this exception to your version of the file(s),
|
|
|
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
|
|
|
* exception statement from your version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "synccontroller.h"
|
|
|
|
|
2019-01-11 11:05:57 +03:00
|
|
|
#include <algorithm>
|
|
|
|
|
2017-10-14 16:27:21 +03:00
|
|
|
#include <QJsonObject>
|
2019-04-02 10:14:00 +03:00
|
|
|
#include <QMetaObject>
|
2018-09-23 03:21:04 +03:00
|
|
|
#include <QThread>
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2020-04-12 18:08:19 +03:00
|
|
|
#include "base/bittorrent/infohash.h"
|
2019-07-22 19:50:42 +03:00
|
|
|
#include "base/bittorrent/peeraddress.h"
|
2017-10-14 16:27:21 +03:00
|
|
|
#include "base/bittorrent/peerinfo.h"
|
|
|
|
#include "base/bittorrent/session.h"
|
|
|
|
#include "base/bittorrent/torrenthandle.h"
|
2020-05-02 14:23:23 +03:00
|
|
|
#include "base/bittorrent/trackerentry.h"
|
2018-11-18 21:40:37 +03:00
|
|
|
#include "base/global.h"
|
2017-10-14 16:27:21 +03:00
|
|
|
#include "base/net/geoipmanager.h"
|
|
|
|
#include "base/preferences.h"
|
|
|
|
#include "base/utils/string.h"
|
|
|
|
#include "apierror.h"
|
2018-09-23 03:21:04 +03:00
|
|
|
#include "freediskspacechecker.h"
|
2017-10-14 16:27:21 +03:00
|
|
|
#include "isessionmanager.h"
|
|
|
|
#include "serialize/serialize_torrent.h"
|
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
const int FREEDISKSPACE_CHECK_TIMEOUT = 30000;
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
// Sync main data keys
|
|
|
|
const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing";
|
|
|
|
const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
|
|
|
|
const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
// Sync torrent peers keys
|
|
|
|
const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags";
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
// Peer keys
|
|
|
|
const char KEY_PEER_CLIENT[] = "client";
|
|
|
|
const char KEY_PEER_CONNECTION_TYPE[] = "connection";
|
|
|
|
const char KEY_PEER_COUNTRY[] = "country";
|
|
|
|
const char KEY_PEER_COUNTRY_CODE[] = "country_code";
|
|
|
|
const char KEY_PEER_DOWN_SPEED[] = "dl_speed";
|
|
|
|
const char KEY_PEER_FILES[] = "files";
|
|
|
|
const char KEY_PEER_FLAGS[] = "flags";
|
|
|
|
const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc";
|
|
|
|
const char KEY_PEER_IP[] = "ip";
|
|
|
|
const char KEY_PEER_PORT[] = "port";
|
|
|
|
const char KEY_PEER_PROGRESS[] = "progress";
|
|
|
|
const char KEY_PEER_RELEVANCE[] = "relevance";
|
|
|
|
const char KEY_PEER_TOT_DOWN[] = "downloaded";
|
|
|
|
const char KEY_PEER_TOT_UP[] = "uploaded";
|
|
|
|
const char KEY_PEER_UP_SPEED[] = "up_speed";
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
// TransferInfo keys
|
|
|
|
const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
|
|
|
|
const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
|
|
|
|
const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
|
|
|
|
const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
|
|
|
|
const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
|
|
|
|
const char KEY_TRANSFER_FREESPACEONDISK[] = "free_space_on_disk";
|
|
|
|
const char KEY_TRANSFER_UPDATA[] = "up_info_data";
|
|
|
|
const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
|
|
|
|
const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
// Statistics keys
|
|
|
|
const char KEY_TRANSFER_ALLTIME_DL[] = "alltime_dl";
|
|
|
|
const char KEY_TRANSFER_ALLTIME_UL[] = "alltime_ul";
|
|
|
|
const char KEY_TRANSFER_AVERAGE_TIME_QUEUE[] = "average_time_queue";
|
|
|
|
const char KEY_TRANSFER_GLOBAL_RATIO[] = "global_ratio";
|
|
|
|
const char KEY_TRANSFER_QUEUED_IO_JOBS[] = "queued_io_jobs";
|
|
|
|
const char KEY_TRANSFER_READ_CACHE_HITS[] = "read_cache_hits";
|
|
|
|
const char KEY_TRANSFER_READ_CACHE_OVERLOAD[] = "read_cache_overload";
|
|
|
|
const char KEY_TRANSFER_TOTAL_BUFFERS_SIZE[] = "total_buffers_size";
|
|
|
|
const char KEY_TRANSFER_TOTAL_PEER_CONNECTIONS[] = "total_peer_connections";
|
|
|
|
const char KEY_TRANSFER_TOTAL_QUEUED_SIZE[] = "total_queued_size";
|
|
|
|
const char KEY_TRANSFER_TOTAL_WASTE_SESSION[] = "total_wasted_session";
|
|
|
|
const char KEY_TRANSFER_WRITE_CACHE_OVERLOAD[] = "write_cache_overload";
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 09:15:32 +03:00
|
|
|
const char KEY_FULL_UPDATE[] = "full_update";
|
|
|
|
const char KEY_RESPONSE_ID[] = "rid";
|
|
|
|
const char KEY_SUFFIX_REMOVED[] = "_removed";
|
2018-09-23 03:21:04 +03:00
|
|
|
|
2017-10-14 16:27:21 +03:00
|
|
|
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);
|
|
|
|
|
2020-03-16 04:01:44 +03:00
|
|
|
QVariantMap getTransferInfo()
|
2017-10-14 16:27:21 +03:00
|
|
|
{
|
|
|
|
QVariantMap map;
|
2019-07-31 07:40:26 +03:00
|
|
|
const auto *session = BitTorrent::Session::instance();
|
|
|
|
|
|
|
|
const BitTorrent::SessionStatus &sessionStatus = session->status();
|
|
|
|
const BitTorrent::CacheStatus &cacheStatus = session->cacheStatus();
|
2017-10-14 16:27:21 +03:00
|
|
|
map[KEY_TRANSFER_DLSPEED] = sessionStatus.payloadDownloadRate;
|
|
|
|
map[KEY_TRANSFER_DLDATA] = sessionStatus.totalPayloadDownload;
|
|
|
|
map[KEY_TRANSFER_UPSPEED] = sessionStatus.payloadUploadRate;
|
|
|
|
map[KEY_TRANSFER_UPDATA] = sessionStatus.totalPayloadUpload;
|
2019-07-31 07:40:26 +03:00
|
|
|
map[KEY_TRANSFER_DLRATELIMIT] = session->downloadSpeedLimit();
|
|
|
|
map[KEY_TRANSFER_UPRATELIMIT] = session->uploadSpeedLimit();
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-07-31 07:40:26 +03:00
|
|
|
const quint64 atd = session->getAlltimeDL();
|
|
|
|
const quint64 atu = session->getAlltimeUL();
|
2017-10-14 16:27:21 +03:00
|
|
|
map[KEY_TRANSFER_ALLTIME_DL] = atd;
|
|
|
|
map[KEY_TRANSFER_ALLTIME_UL] = atu;
|
|
|
|
map[KEY_TRANSFER_TOTAL_WASTE_SESSION] = sessionStatus.totalWasted;
|
|
|
|
map[KEY_TRANSFER_GLOBAL_RATIO] = ((atd > 0) && (atu > 0)) ? Utils::String::fromDouble(static_cast<qreal>(atu) / atd, 2) : "-";
|
|
|
|
map[KEY_TRANSFER_TOTAL_PEER_CONNECTIONS] = sessionStatus.peersCount;
|
|
|
|
|
2020-12-24 06:54:33 +03:00
|
|
|
const qreal readRatio = cacheStatus.readRatio; // TODO: remove when LIBTORRENT_VERSION_NUM >= 20000
|
2018-02-23 22:46:47 +03:00
|
|
|
map[KEY_TRANSFER_READ_CACHE_HITS] = (readRatio > 0) ? Utils::String::fromDouble(100 * readRatio, 2) : "0";
|
2017-10-14 16:27:21 +03:00
|
|
|
map[KEY_TRANSFER_TOTAL_BUFFERS_SIZE] = cacheStatus.totalUsedBuffers * 16 * 1024;
|
|
|
|
|
2020-03-16 04:01:44 +03:00
|
|
|
map[KEY_TRANSFER_WRITE_CACHE_OVERLOAD] = ((sessionStatus.diskWriteQueue > 0) && (sessionStatus.peersCount > 0))
|
|
|
|
? Utils::String::fromDouble((100. * sessionStatus.diskWriteQueue / sessionStatus.peersCount), 2)
|
|
|
|
: QLatin1String("0");
|
|
|
|
map[KEY_TRANSFER_READ_CACHE_OVERLOAD] = ((sessionStatus.diskReadQueue > 0) && (sessionStatus.peersCount > 0))
|
|
|
|
? Utils::String::fromDouble((100. * sessionStatus.diskReadQueue / sessionStatus.peersCount), 2)
|
|
|
|
: QLatin1String("0");
|
2017-10-14 16:27:21 +03:00
|
|
|
|
|
|
|
map[KEY_TRANSFER_QUEUED_IO_JOBS] = cacheStatus.jobQueueLength;
|
|
|
|
map[KEY_TRANSFER_AVERAGE_TIME_QUEUE] = cacheStatus.averageJobTime;
|
|
|
|
map[KEY_TRANSFER_TOTAL_QUEUED_SIZE] = cacheStatus.queuedBytes;
|
|
|
|
|
|
|
|
map[KEY_TRANSFER_DHT_NODES] = sessionStatus.dhtNodes;
|
2019-07-31 07:40:26 +03:00
|
|
|
map[KEY_TRANSFER_CONNECTION_STATUS] = session->isListening()
|
|
|
|
? (sessionStatus.hasIncomingConnections ? "connected" : "firewalled")
|
|
|
|
: "disconnected";
|
|
|
|
|
2017-10-14 16:27:21 +03:00
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare two structures (prevData, data) and calculate difference (syncData).
|
|
|
|
// Structures encoded as map.
|
|
|
|
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData)
|
|
|
|
{
|
|
|
|
// initialize output variable
|
|
|
|
syncData.clear();
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
for (auto i = data.cbegin(); i != data.cend(); ++i)
|
|
|
|
{
|
2018-03-06 17:50:10 +03:00
|
|
|
const QString &key = i.key();
|
|
|
|
const QVariant &value = i.value();
|
2019-07-31 07:40:26 +03:00
|
|
|
QVariantList removedItems;
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
switch (static_cast<QMetaType::Type>(value.type()))
|
|
|
|
{
|
|
|
|
case QMetaType::QVariantMap:
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
QVariantMap map;
|
2018-03-06 17:50:10 +03:00
|
|
|
processMap(prevData[key].toMap(), value.toMap(), map);
|
2017-10-14 16:27:21 +03:00
|
|
|
if (!map.isEmpty())
|
|
|
|
syncData[key] = map;
|
|
|
|
}
|
|
|
|
break;
|
2020-11-16 10:02:11 +03:00
|
|
|
case QMetaType::QVariantHash:
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
QVariantMap map;
|
2018-03-06 17:50:10 +03:00
|
|
|
processHash(prevData[key].toHash(), value.toHash(), map, removedItems);
|
2017-10-14 16:27:21 +03:00
|
|
|
if (!map.isEmpty())
|
|
|
|
syncData[key] = map;
|
|
|
|
if (!removedItems.isEmpty())
|
|
|
|
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
|
|
|
|
}
|
|
|
|
break;
|
2020-11-16 10:02:11 +03:00
|
|
|
case QMetaType::QVariantList:
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
QVariantList list;
|
2018-03-06 17:50:10 +03:00
|
|
|
processList(prevData[key].toList(), value.toList(), list, removedItems);
|
2017-10-14 16:27:21 +03:00
|
|
|
if (!list.isEmpty())
|
|
|
|
syncData[key] = list;
|
|
|
|
if (!removedItems.isEmpty())
|
|
|
|
syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case QMetaType::QString:
|
|
|
|
case QMetaType::LongLong:
|
|
|
|
case QMetaType::Float:
|
|
|
|
case QMetaType::Int:
|
|
|
|
case QMetaType::Bool:
|
|
|
|
case QMetaType::Double:
|
|
|
|
case QMetaType::ULongLong:
|
|
|
|
case QMetaType::UInt:
|
|
|
|
case QMetaType::QDateTime:
|
2018-03-06 17:50:10 +03:00
|
|
|
if (prevData[key] != value)
|
|
|
|
syncData[key] = value;
|
2017-10-14 16:27:21 +03:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Q_ASSERT_X(false, "processMap"
|
|
|
|
, QString("Unexpected type: %1")
|
2018-03-06 17:50:10 +03:00
|
|
|
.arg(QMetaType::typeName(static_cast<QMetaType::Type>(value.type())))
|
2017-10-14 16:27:21 +03:00
|
|
|
.toUtf8().constData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems).
|
|
|
|
// Structures encoded as map.
|
|
|
|
// Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items.
|
|
|
|
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems)
|
|
|
|
{
|
|
|
|
// initialize output variables
|
|
|
|
syncData.clear();
|
|
|
|
removedItems.clear();
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (prevData.isEmpty())
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
// If list was empty before, then difference is a whole new list.
|
2018-03-06 17:50:10 +03:00
|
|
|
for (auto i = data.cbegin(); i != data.cend(); ++i)
|
|
|
|
syncData[i.key()] = i.value();
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
2020-11-16 10:02:11 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (auto i = data.cbegin(); i != data.cend(); ++i)
|
|
|
|
{
|
|
|
|
switch (i.value().type())
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
case QVariant::Map:
|
2020-11-16 10:02:11 +03:00
|
|
|
if (!prevData.contains(i.key()))
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
// new list item found - append it to syncData
|
2018-03-06 17:50:10 +03:00
|
|
|
syncData[i.key()] = i.value();
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
2020-11-16 10:02:11 +03:00
|
|
|
else
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
QVariantMap map;
|
2018-03-06 17:50:10 +03:00
|
|
|
processMap(prevData[i.key()].toMap(), i.value().toMap(), map);
|
2017-10-14 16:27:21 +03:00
|
|
|
// existing list item found - remove it from prevData
|
2018-03-06 17:50:10 +03:00
|
|
|
prevData.remove(i.key());
|
2020-11-16 10:02:11 +03:00
|
|
|
if (!map.isEmpty())
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
// changed list item found - append its changes to syncData
|
2018-03-06 17:50:10 +03:00
|
|
|
syncData[i.key()] = map;
|
2020-05-03 13:00:58 +03:00
|
|
|
}
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
|
|
|
break;
|
2020-05-02 14:23:23 +03:00
|
|
|
case QVariant::StringList:
|
2020-11-16 10:02:11 +03:00
|
|
|
if (!prevData.contains(i.key()))
|
|
|
|
{
|
2020-05-02 14:23:23 +03:00
|
|
|
// new list item found - append it to syncData
|
|
|
|
syncData[i.key()] = i.value();
|
|
|
|
}
|
2020-11-16 10:02:11 +03:00
|
|
|
else
|
|
|
|
{
|
2020-05-02 14:23:23 +03:00
|
|
|
QVariantList list;
|
|
|
|
QVariantList removedList;
|
|
|
|
processList(prevData[i.key()].toList(), i.value().toList(), list, removedList);
|
|
|
|
// existing list item found - remove it from prevData
|
|
|
|
prevData.remove(i.key());
|
2020-11-16 10:02:11 +03:00
|
|
|
if (!list.isEmpty() || !removedList.isEmpty())
|
|
|
|
{
|
2020-05-02 14:23:23 +03:00
|
|
|
// changed list item found - append entire list to syncData
|
|
|
|
syncData[i.key()] = i.value();
|
2020-05-03 13:00:58 +03:00
|
|
|
}
|
2020-05-02 14:23:23 +03:00
|
|
|
}
|
|
|
|
break;
|
2017-10-14 16:27:21 +03:00
|
|
|
default:
|
|
|
|
Q_ASSERT(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (!prevData.isEmpty())
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
// prevData contains only items that are missing now -
|
|
|
|
// put them in removedItems
|
2018-03-06 17:50:10 +03:00
|
|
|
for (auto i = prevData.cbegin(); i != prevData.cend(); ++i)
|
|
|
|
removedItems << i.key();
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems).
|
|
|
|
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems)
|
|
|
|
{
|
|
|
|
// initialize output variables
|
|
|
|
syncData.clear();
|
|
|
|
removedItems.clear();
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (prevData.isEmpty())
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
// If list was empty before, then difference is a whole new list.
|
|
|
|
syncData = data;
|
|
|
|
}
|
2020-11-16 10:02:11 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (const QVariant &item : data)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
if (!prevData.contains(item))
|
|
|
|
// new list item found - append it to syncData
|
|
|
|
syncData.append(item);
|
|
|
|
else
|
|
|
|
// unchanged list item found - remove it from prevData
|
|
|
|
prevData.removeOne(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!prevData.isEmpty())
|
|
|
|
// prevData contains only items that are missing now -
|
|
|
|
// put them in removedItems
|
|
|
|
removedItems = prevData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
|
|
|
|
{
|
|
|
|
QVariantMap syncData;
|
|
|
|
bool fullUpdate = true;
|
|
|
|
int lastResponseId = 0;
|
2020-11-16 10:02:11 +03:00
|
|
|
if (acceptedResponseId > 0)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
|
|
|
|
|
|
|
|
if (lastResponseId == acceptedResponseId)
|
|
|
|
lastAcceptedData = lastData;
|
|
|
|
|
|
|
|
int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt();
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (lastAcceptedResponseId == acceptedResponseId)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
processMap(lastAcceptedData, data, syncData);
|
|
|
|
fullUpdate = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (fullUpdate)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
lastAcceptedData.clear();
|
|
|
|
syncData = data;
|
|
|
|
syncData[KEY_FULL_UPDATE] = true;
|
|
|
|
}
|
|
|
|
|
2019-07-31 07:40:26 +03:00
|
|
|
lastResponseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000
|
2017-10-14 16:27:21 +03:00
|
|
|
lastData = data;
|
|
|
|
lastData[KEY_RESPONSE_ID] = lastResponseId;
|
|
|
|
syncData[KEY_RESPONSE_ID] = lastResponseId;
|
|
|
|
|
|
|
|
return syncData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 03:21:04 +03:00
|
|
|
SyncController::SyncController(ISessionManager *sessionManager, QObject *parent)
|
|
|
|
: APIController(sessionManager, parent)
|
|
|
|
{
|
|
|
|
m_freeDiskSpaceThread = new QThread(this);
|
|
|
|
m_freeDiskSpaceChecker = new FreeDiskSpaceChecker();
|
|
|
|
m_freeDiskSpaceChecker->moveToThread(m_freeDiskSpaceThread);
|
|
|
|
|
|
|
|
connect(m_freeDiskSpaceThread, &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater);
|
|
|
|
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, &SyncController::freeDiskSpaceSizeUpdated);
|
|
|
|
|
|
|
|
m_freeDiskSpaceThread->start();
|
2019-04-02 10:14:00 +03:00
|
|
|
invokeChecker();
|
2018-10-23 06:17:46 +03:00
|
|
|
m_freeDiskSpaceElapsedTimer.start();
|
2018-09-23 03:21:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
SyncController::~SyncController()
|
|
|
|
{
|
|
|
|
m_freeDiskSpaceThread->quit();
|
|
|
|
m_freeDiskSpaceThread->wait();
|
|
|
|
}
|
|
|
|
|
2017-10-14 16:27:21 +03:00
|
|
|
// The function returns the changed data from the server to synchronize with the web client.
|
|
|
|
// Return value is map in JSON format.
|
|
|
|
// Map contain the key:
|
|
|
|
// - "Rid": ID response
|
|
|
|
// Map can contain the keys:
|
|
|
|
// - "full_update": full data update flag
|
|
|
|
// - "torrents": dictionary contains information about torrents.
|
|
|
|
// - "torrents_removed": a list of hashes of removed torrents
|
2018-07-23 07:53:56 +03:00
|
|
|
// - "categories": map of categories info
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "categories_removed": list of removed categories
|
2020-05-02 14:23:23 +03:00
|
|
|
// - "trackers": dictionary contains information about trackers
|
|
|
|
// - "trackers_removed": a list of removed trackers
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "server_state": map contains information about the state of the server
|
|
|
|
// The keys of the 'torrents' dictionary are hashes of torrents.
|
|
|
|
// Each value of the 'torrents' dictionary contains map. The map can contain following keys:
|
|
|
|
// - "name": Torrent name
|
|
|
|
// - "size": Torrent size
|
2018-04-09 16:52:37 +03:00
|
|
|
// - "progress": Torrent progress
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "dlspeed": Torrent download speed
|
|
|
|
// - "upspeed": Torrent upload speed
|
2018-12-08 02:01:09 +03:00
|
|
|
// - "priority": Torrent queue position (-1 if queuing is disabled)
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "num_seeds": Torrent seeds connected to
|
|
|
|
// - "num_complete": Torrent seeds in the swarm
|
|
|
|
// - "num_leechs": Torrent leechers connected to
|
|
|
|
// - "num_incomplete": Torrent leechers in the swarm
|
|
|
|
// - "ratio": Torrent share ratio
|
|
|
|
// - "eta": Torrent ETA
|
|
|
|
// - "state": Torrent state
|
|
|
|
// - "seq_dl": Torrent sequential download state
|
|
|
|
// - "f_l_piece_prio": Torrent first last piece priority state
|
|
|
|
// - "completion_on": Torrent copletion time
|
|
|
|
// - "tracker": Torrent tracker
|
|
|
|
// - "dl_limit": Torrent download limit
|
|
|
|
// - "up_limit": Torrent upload limit
|
|
|
|
// - "downloaded": Amount of data downloaded
|
|
|
|
// - "uploaded": Amount of data uploaded
|
|
|
|
// - "downloaded_session": Amount of data downloaded since program open
|
|
|
|
// - "uploaded_session": Amount of data uploaded since program open
|
|
|
|
// - "amount_left": Amount of data left to download
|
|
|
|
// - "save_path": Torrent save path
|
|
|
|
// - "completed": Amount of data completed
|
2018-03-14 07:37:48 +03:00
|
|
|
// - "max_ratio": Upload max share ratio
|
2018-03-14 07:41:16 +03:00
|
|
|
// - "max_seeding_time": Upload max seeding time
|
|
|
|
// - "ratio_limit": Upload share ratio limit
|
|
|
|
// - "seeding_time_limit": Upload seeding time limit
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "seen_complete": Indicates the time when the torrent was last seen complete/whole
|
|
|
|
// - "last_activity": Last time when a chunk was downloaded/uploaded
|
|
|
|
// - "total_size": Size including unwanted data
|
|
|
|
// Server state map may contain the following keys:
|
|
|
|
// - "connection_status": connection status
|
|
|
|
// - "dht_nodes": DHT nodes count
|
|
|
|
// - "dl_info_data": bytes downloaded
|
|
|
|
// - "dl_info_speed": download speed
|
|
|
|
// - "dl_rate_limit: download rate limit
|
|
|
|
// - "up_info_data: bytes uploaded
|
|
|
|
// - "up_info_speed: upload speed
|
|
|
|
// - "up_rate_limit: upload speed limit
|
2018-12-08 02:01:09 +03:00
|
|
|
// - "queueing": queue system usage flag
|
2017-10-14 16:27:21 +03:00
|
|
|
// - "refresh_interval": torrents table refresh interval
|
2018-09-23 03:21:04 +03:00
|
|
|
// - "free_space_on_disk": Free space on the default save path
|
2017-10-14 16:27:21 +03:00
|
|
|
// GET param:
|
|
|
|
// - rid (int): last response id
|
|
|
|
void SyncController::maindataAction()
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const auto *session = BitTorrent::Session::instance();
|
2017-10-14 16:27:21 +03:00
|
|
|
|
|
|
|
QVariantMap data;
|
|
|
|
|
2019-07-31 07:40:26 +03:00
|
|
|
QVariantMap lastResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastResponse")).toMap();
|
|
|
|
QVariantMap lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastAcceptedResponse")).toMap();
|
|
|
|
|
|
|
|
QVariantHash torrents;
|
2020-05-02 14:23:23 +03:00
|
|
|
QHash<QString, QStringList> trackers;
|
2020-11-16 10:02:11 +03:00
|
|
|
for (const BitTorrent::TorrentHandle *torrent : asConst(session->torrents()))
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const BitTorrent::InfoHash torrentHash = torrent->hash();
|
2017-10-14 16:27:21 +03:00
|
|
|
|
|
|
|
QVariantMap map = serialize(*torrent);
|
|
|
|
map.remove(KEY_TORRENT_HASH);
|
|
|
|
|
|
|
|
// 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.
|
2019-07-31 07:40:26 +03:00
|
|
|
const auto iterTorrents = lastResponse.find("torrents");
|
2020-11-16 10:02:11 +03:00
|
|
|
if (iterTorrents != lastResponse.end())
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const QVariantHash lastResponseTorrents = iterTorrents->toHash();
|
|
|
|
const auto iterHash = lastResponseTorrents.find(torrentHash);
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (iterHash != lastResponseTorrents.end())
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const QVariantMap torrentData = iterHash->toMap();
|
|
|
|
const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME);
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (iterLastActivity != torrentData.end())
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const int lastValue = iterLastActivity->toInt();
|
|
|
|
if (qAbs(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toInt()) < 15)
|
|
|
|
map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue;
|
|
|
|
}
|
|
|
|
}
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
|
|
|
|
2020-05-03 13:00:58 +03:00
|
|
|
for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers()))
|
2020-05-02 14:23:23 +03:00
|
|
|
trackers[tracker.url()] << torrentHash;
|
|
|
|
|
2019-07-31 07:40:26 +03:00
|
|
|
torrents[torrentHash] = map;
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
|
|
|
data["torrents"] = torrents;
|
|
|
|
|
2018-07-23 08:49:34 +03:00
|
|
|
QVariantHash categories;
|
2019-10-01 11:23:32 +03:00
|
|
|
const QStringMap categoriesList = session->categories();
|
2020-11-16 10:02:11 +03:00
|
|
|
for (auto it = categoriesList.cbegin(); it != categoriesList.cend(); ++it)
|
|
|
|
{
|
2019-07-31 07:40:26 +03:00
|
|
|
const QString &key = it.key();
|
2020-11-16 10:02:11 +03:00
|
|
|
categories[key] = QVariantMap
|
|
|
|
{
|
2018-07-23 07:53:56 +03:00
|
|
|
{"name", key},
|
2018-07-23 08:49:34 +03:00
|
|
|
{"savePath", it.value()}
|
2018-07-23 07:53:56 +03:00
|
|
|
};
|
|
|
|
}
|
2018-07-23 08:49:34 +03:00
|
|
|
data["categories"] = categories;
|
2017-10-14 16:27:21 +03:00
|
|
|
|
2019-05-30 09:03:27 +03:00
|
|
|
QVariantList tags;
|
|
|
|
for (const QString &tag : asConst(session->tags()))
|
|
|
|
tags << tag;
|
|
|
|
data["tags"] = tags;
|
|
|
|
|
2020-05-02 14:23:23 +03:00
|
|
|
QVariantHash trackersHash;
|
2020-11-16 10:02:11 +03:00
|
|
|
for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i)
|
|
|
|
{
|
2020-05-02 14:23:23 +03:00
|
|
|
trackersHash[i.key()] = i.value();
|
|
|
|
}
|
|
|
|
data["trackers"] = trackersHash;
|
|
|
|
|
2020-03-16 04:01:44 +03:00
|
|
|
QVariantMap serverState = getTransferInfo();
|
2018-09-23 03:21:04 +03:00
|
|
|
serverState[KEY_TRANSFER_FREESPACEONDISK] = getFreeDiskSpace();
|
2017-10-14 16:27:21 +03:00
|
|
|
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["server_state"] = serverState;
|
|
|
|
|
|
|
|
const int acceptedResponseId {params()["rid"].toInt()};
|
|
|
|
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
|
|
|
|
|
|
|
|
sessionManager()->session()->setData(QLatin1String("syncMainDataLastResponse"), lastResponse);
|
|
|
|
sessionManager()->session()->setData(QLatin1String("syncMainDataLastAcceptedResponse"), lastAcceptedResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
// GET param:
|
|
|
|
// - hash (string): torrent hash
|
|
|
|
// - rid (int): last response id
|
|
|
|
void SyncController::torrentPeersAction()
|
|
|
|
{
|
|
|
|
auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastResponse")).toMap();
|
|
|
|
auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastAcceptedResponse")).toMap();
|
|
|
|
|
|
|
|
const QString hash {params()["hash"]};
|
2019-07-31 07:40:26 +03:00
|
|
|
const BitTorrent::TorrentHandle *torrent = BitTorrent::Session::instance()->findTorrent(hash);
|
2017-10-14 16:27:21 +03:00
|
|
|
if (!torrent)
|
|
|
|
throw APIError(APIErrorType::NotFound);
|
|
|
|
|
|
|
|
QVariantMap data;
|
|
|
|
QVariantHash peers;
|
2019-08-02 07:55:06 +03:00
|
|
|
|
|
|
|
const QVector<BitTorrent::PeerInfo> peersList = torrent->peers();
|
2019-07-31 07:40:26 +03:00
|
|
|
|
2017-10-14 16:27:21 +03:00
|
|
|
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
|
|
|
|
|
|
|
|
data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries;
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
for (const BitTorrent::PeerInfo &pi : peersList)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
if (pi.address().ip.isNull()) continue;
|
2019-07-31 07:40:26 +03:00
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
QVariantMap peer =
|
|
|
|
{
|
2019-07-31 15:00:04 +03:00
|
|
|
{KEY_PEER_IP, pi.address().ip.toString()},
|
|
|
|
{KEY_PEER_PORT, pi.address().port},
|
|
|
|
{KEY_PEER_CLIENT, pi.client()},
|
|
|
|
{KEY_PEER_PROGRESS, pi.progress()},
|
|
|
|
{KEY_PEER_DOWN_SPEED, pi.payloadDownSpeed()},
|
|
|
|
{KEY_PEER_UP_SPEED, pi.payloadUpSpeed()},
|
|
|
|
{KEY_PEER_TOT_DOWN, pi.totalDownload()},
|
|
|
|
{KEY_PEER_TOT_UP, pi.totalUpload()},
|
|
|
|
{KEY_PEER_CONNECTION_TYPE, pi.connectionType()},
|
|
|
|
{KEY_PEER_FLAGS, pi.flags()},
|
|
|
|
{KEY_PEER_FLAGS_DESCRIPTION, pi.flagsDescription()},
|
|
|
|
{KEY_PEER_RELEVANCE, pi.relevance()},
|
|
|
|
{KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n')}
|
|
|
|
};
|
|
|
|
|
2020-11-16 10:02:11 +03:00
|
|
|
if (resolvePeerCountries)
|
|
|
|
{
|
2017-10-14 16:27:21 +03:00
|
|
|
peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
|
|
|
|
peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country());
|
|
|
|
}
|
|
|
|
|
2020-10-20 13:40:26 +03:00
|
|
|
peers[pi.address().toString()] = peer;
|
2017-10-14 16:27:21 +03:00
|
|
|
}
|
|
|
|
data["peers"] = peers;
|
|
|
|
|
|
|
|
const int acceptedResponseId {params()["rid"].toInt()};
|
|
|
|
setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
|
|
|
|
|
|
|
|
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastResponse"), lastResponse);
|
|
|
|
sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastAcceptedResponse"), lastAcceptedResponse);
|
|
|
|
}
|
2018-09-23 03:21:04 +03:00
|
|
|
|
|
|
|
qint64 SyncController::getFreeDiskSpace()
|
|
|
|
{
|
2020-11-16 10:02:11 +03:00
|
|
|
if (m_freeDiskSpaceElapsedTimer.hasExpired(FREEDISKSPACE_CHECK_TIMEOUT))
|
|
|
|
{
|
2019-04-02 10:14:00 +03:00
|
|
|
invokeChecker();
|
2018-10-23 06:17:46 +03:00
|
|
|
m_freeDiskSpaceElapsedTimer.restart();
|
2018-09-23 03:21:04 +03:00
|
|
|
}
|
2018-10-23 06:17:46 +03:00
|
|
|
|
2018-09-23 03:21:04 +03:00
|
|
|
return m_freeDiskSpace;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SyncController::freeDiskSpaceSizeUpdated(qint64 freeSpaceSize)
|
|
|
|
{
|
|
|
|
m_freeDiskSpace = freeSpaceSize;
|
|
|
|
}
|
2019-04-02 10:14:00 +03:00
|
|
|
|
|
|
|
void SyncController::invokeChecker() const
|
|
|
|
{
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
|
|
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check, Qt::QueuedConnection);
|
|
|
|
#else
|
|
|
|
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, "check", Qt::QueuedConnection);
|
|
|
|
#endif
|
|
|
|
}
|