qBittorrent
synccontroller.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "synccontroller.h"
30 
31 #include <algorithm>
32 
33 #include <QJsonObject>
34 #include <QMetaObject>
35 #include <QThread>
36 
43 #include "base/global.h"
44 #include "base/net/geoipmanager.h"
45 #include "base/preferences.h"
46 #include "base/utils/string.h"
47 #include "apierror.h"
48 #include "freediskspacechecker.h"
49 #include "isessionmanager.h"
51 
52 namespace
53 {
54  const int FREEDISKSPACE_CHECK_TIMEOUT = 30000;
55 
56  // Sync main data keys
57  const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing";
58  const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval";
59  const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits";
60 
61  // Sync torrent peers keys
62  const char KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS[] = "show_flags";
63 
64  // Peer keys
65  const char KEY_PEER_CLIENT[] = "client";
66  const char KEY_PEER_CONNECTION_TYPE[] = "connection";
67  const char KEY_PEER_COUNTRY[] = "country";
68  const char KEY_PEER_COUNTRY_CODE[] = "country_code";
69  const char KEY_PEER_DOWN_SPEED[] = "dl_speed";
70  const char KEY_PEER_FILES[] = "files";
71  const char KEY_PEER_FLAGS[] = "flags";
72  const char KEY_PEER_FLAGS_DESCRIPTION[] = "flags_desc";
73  const char KEY_PEER_IP[] = "ip";
74  const char KEY_PEER_PORT[] = "port";
75  const char KEY_PEER_PROGRESS[] = "progress";
76  const char KEY_PEER_RELEVANCE[] = "relevance";
77  const char KEY_PEER_TOT_DOWN[] = "downloaded";
78  const char KEY_PEER_TOT_UP[] = "uploaded";
79  const char KEY_PEER_UP_SPEED[] = "up_speed";
80 
81  // TransferInfo keys
82  const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status";
83  const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes";
84  const char KEY_TRANSFER_DLDATA[] = "dl_info_data";
85  const char KEY_TRANSFER_DLRATELIMIT[] = "dl_rate_limit";
86  const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed";
87  const char KEY_TRANSFER_FREESPACEONDISK[] = "free_space_on_disk";
88  const char KEY_TRANSFER_UPDATA[] = "up_info_data";
89  const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit";
90  const char KEY_TRANSFER_UPSPEED[] = "up_info_speed";
91 
92  // Statistics keys
93  const char KEY_TRANSFER_ALLTIME_DL[] = "alltime_dl";
94  const char KEY_TRANSFER_ALLTIME_UL[] = "alltime_ul";
95  const char KEY_TRANSFER_AVERAGE_TIME_QUEUE[] = "average_time_queue";
96  const char KEY_TRANSFER_GLOBAL_RATIO[] = "global_ratio";
97  const char KEY_TRANSFER_QUEUED_IO_JOBS[] = "queued_io_jobs";
98  const char KEY_TRANSFER_READ_CACHE_HITS[] = "read_cache_hits";
99  const char KEY_TRANSFER_READ_CACHE_OVERLOAD[] = "read_cache_overload";
100  const char KEY_TRANSFER_TOTAL_BUFFERS_SIZE[] = "total_buffers_size";
101  const char KEY_TRANSFER_TOTAL_PEER_CONNECTIONS[] = "total_peer_connections";
102  const char KEY_TRANSFER_TOTAL_QUEUED_SIZE[] = "total_queued_size";
103  const char KEY_TRANSFER_TOTAL_WASTE_SESSION[] = "total_wasted_session";
104  const char KEY_TRANSFER_WRITE_CACHE_OVERLOAD[] = "write_cache_overload";
105 
106  const char KEY_FULL_UPDATE[] = "full_update";
107  const char KEY_RESPONSE_ID[] = "rid";
108  const char KEY_SUFFIX_REMOVED[] = "_removed";
109 
110  void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData);
111  void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems);
112  void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems);
113  QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData);
114 
115  QVariantMap getTransferInfo()
116  {
117  QVariantMap map;
118  const auto *session = BitTorrent::Session::instance();
119 
120  const BitTorrent::SessionStatus &sessionStatus = session->status();
121  const BitTorrent::CacheStatus &cacheStatus = session->cacheStatus();
122  map[KEY_TRANSFER_DLSPEED] = sessionStatus.payloadDownloadRate;
123  map[KEY_TRANSFER_DLDATA] = sessionStatus.totalPayloadDownload;
124  map[KEY_TRANSFER_UPSPEED] = sessionStatus.payloadUploadRate;
125  map[KEY_TRANSFER_UPDATA] = sessionStatus.totalPayloadUpload;
126  map[KEY_TRANSFER_DLRATELIMIT] = session->downloadSpeedLimit();
127  map[KEY_TRANSFER_UPRATELIMIT] = session->uploadSpeedLimit();
128 
129  const quint64 atd = session->getAlltimeDL();
130  const quint64 atu = session->getAlltimeUL();
131  map[KEY_TRANSFER_ALLTIME_DL] = atd;
132  map[KEY_TRANSFER_ALLTIME_UL] = atu;
133  map[KEY_TRANSFER_TOTAL_WASTE_SESSION] = sessionStatus.totalWasted;
134  map[KEY_TRANSFER_GLOBAL_RATIO] = ((atd > 0) && (atu > 0)) ? Utils::String::fromDouble(static_cast<qreal>(atu) / atd, 2) : "-";
135  map[KEY_TRANSFER_TOTAL_PEER_CONNECTIONS] = sessionStatus.peersCount;
136 
137  const qreal readRatio = cacheStatus.readRatio; // TODO: remove when LIBTORRENT_VERSION_NUM >= 20000
138  map[KEY_TRANSFER_READ_CACHE_HITS] = (readRatio > 0) ? Utils::String::fromDouble(100 * readRatio, 2) : "0";
139  map[KEY_TRANSFER_TOTAL_BUFFERS_SIZE] = cacheStatus.totalUsedBuffers * 16 * 1024;
140 
141  map[KEY_TRANSFER_WRITE_CACHE_OVERLOAD] = ((sessionStatus.diskWriteQueue > 0) && (sessionStatus.peersCount > 0))
142  ? Utils::String::fromDouble((100. * sessionStatus.diskWriteQueue / sessionStatus.peersCount), 2)
143  : QLatin1String("0");
144  map[KEY_TRANSFER_READ_CACHE_OVERLOAD] = ((sessionStatus.diskReadQueue > 0) && (sessionStatus.peersCount > 0))
145  ? Utils::String::fromDouble((100. * sessionStatus.diskReadQueue / sessionStatus.peersCount), 2)
146  : QLatin1String("0");
147 
148  map[KEY_TRANSFER_QUEUED_IO_JOBS] = cacheStatus.jobQueueLength;
150  map[KEY_TRANSFER_TOTAL_QUEUED_SIZE] = cacheStatus.queuedBytes;
151 
152  map[KEY_TRANSFER_DHT_NODES] = sessionStatus.dhtNodes;
153  map[KEY_TRANSFER_CONNECTION_STATUS] = session->isListening()
154  ? (sessionStatus.hasIncomingConnections ? "connected" : "firewalled")
155  : "disconnected";
156 
157  return map;
158  }
159 
160  // Compare two structures (prevData, data) and calculate difference (syncData).
161  // Structures encoded as map.
162  void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData)
163  {
164  // initialize output variable
165  syncData.clear();
166 
167  for (auto i = data.cbegin(); i != data.cend(); ++i)
168  {
169  const QString &key = i.key();
170  const QVariant &value = i.value();
171  QVariantList removedItems;
172 
173  switch (static_cast<QMetaType::Type>(value.type()))
174  {
175  case QMetaType::QVariantMap:
176  {
177  QVariantMap map;
178  processMap(prevData[key].toMap(), value.toMap(), map);
179  if (!map.isEmpty())
180  syncData[key] = map;
181  }
182  break;
183  case QMetaType::QVariantHash:
184  {
185  QVariantMap map;
186  processHash(prevData[key].toHash(), value.toHash(), map, removedItems);
187  if (!map.isEmpty())
188  syncData[key] = map;
189  if (!removedItems.isEmpty())
190  syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
191  }
192  break;
193  case QMetaType::QVariantList:
194  {
195  QVariantList list;
196  processList(prevData[key].toList(), value.toList(), list, removedItems);
197  if (!list.isEmpty())
198  syncData[key] = list;
199  if (!removedItems.isEmpty())
200  syncData[key + KEY_SUFFIX_REMOVED] = removedItems;
201  }
202  break;
203  case QMetaType::QString:
204  case QMetaType::LongLong:
205  case QMetaType::Float:
206  case QMetaType::Int:
207  case QMetaType::Bool:
208  case QMetaType::Double:
209  case QMetaType::ULongLong:
210  case QMetaType::UInt:
211  case QMetaType::QDateTime:
212  case QMetaType::Nullptr:
213  if (prevData[key] != value)
214  syncData[key] = value;
215  break;
216  default:
217  Q_ASSERT_X(false, "processMap"
218  , QString("Unexpected type: %1")
219  .arg(QMetaType::typeName(static_cast<QMetaType::Type>(value.type())))
220  .toUtf8().constData());
221  }
222  }
223  }
224 
225  // Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems).
226  // Structures encoded as map.
227  // Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items.
228  void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems)
229  {
230  // initialize output variables
231  syncData.clear();
232  removedItems.clear();
233 
234  if (prevData.isEmpty())
235  {
236  // If list was empty before, then difference is a whole new list.
237  for (auto i = data.cbegin(); i != data.cend(); ++i)
238  syncData[i.key()] = i.value();
239  }
240  else
241  {
242  for (auto i = data.cbegin(); i != data.cend(); ++i)
243  {
244  switch (i.value().type())
245  {
246  case QVariant::Map:
247  if (!prevData.contains(i.key()))
248  {
249  // new list item found - append it to syncData
250  syncData[i.key()] = i.value();
251  }
252  else
253  {
254  QVariantMap map;
255  processMap(prevData[i.key()].toMap(), i.value().toMap(), map);
256  // existing list item found - remove it from prevData
257  prevData.remove(i.key());
258  if (!map.isEmpty())
259  {
260  // changed list item found - append its changes to syncData
261  syncData[i.key()] = map;
262  }
263  }
264  break;
265  case QVariant::StringList:
266  if (!prevData.contains(i.key()))
267  {
268  // new list item found - append it to syncData
269  syncData[i.key()] = i.value();
270  }
271  else
272  {
273  QVariantList list;
274  QVariantList removedList;
275  processList(prevData[i.key()].toList(), i.value().toList(), list, removedList);
276  // existing list item found - remove it from prevData
277  prevData.remove(i.key());
278  if (!list.isEmpty() || !removedList.isEmpty())
279  {
280  // changed list item found - append entire list to syncData
281  syncData[i.key()] = i.value();
282  }
283  }
284  break;
285  default:
286  Q_ASSERT(false);
287  break;
288  }
289  }
290 
291  if (!prevData.isEmpty())
292  {
293  // prevData contains only items that are missing now -
294  // put them in removedItems
295  for (auto i = prevData.cbegin(); i != prevData.cend(); ++i)
296  removedItems << i.key();
297  }
298  }
299  }
300 
301  // Compare two lists of simple value (prevData, data) and calculate difference (syncData, removedItems).
302  void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems)
303  {
304  // initialize output variables
305  syncData.clear();
306  removedItems.clear();
307 
308  if (prevData.isEmpty())
309  {
310  // If list was empty before, then difference is a whole new list.
311  syncData = data;
312  }
313  else
314  {
315  for (const QVariant &item : data)
316  {
317  if (!prevData.contains(item))
318  // new list item found - append it to syncData
319  syncData.append(item);
320  else
321  // unchanged list item found - remove it from prevData
322  prevData.removeOne(item);
323  }
324 
325  if (!prevData.isEmpty())
326  // prevData contains only items that are missing now -
327  // put them in removedItems
328  removedItems = prevData;
329  }
330  }
331 
332  QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
333  {
334  QVariantMap syncData;
335  bool fullUpdate = true;
336  int lastResponseId = 0;
337  if (acceptedResponseId > 0)
338  {
339  lastResponseId = lastData[KEY_RESPONSE_ID].toInt();
340 
341  if (lastResponseId == acceptedResponseId)
342  lastAcceptedData = lastData;
343 
344  int lastAcceptedResponseId = lastAcceptedData[KEY_RESPONSE_ID].toInt();
345 
346  if (lastAcceptedResponseId == acceptedResponseId)
347  {
348  processMap(lastAcceptedData, data, syncData);
349  fullUpdate = false;
350  }
351  }
352 
353  if (fullUpdate)
354  {
355  lastAcceptedData.clear();
356  syncData = data;
357  syncData[KEY_FULL_UPDATE] = true;
358  }
359 
360  lastResponseId = (lastResponseId % 1000000) + 1; // cycle between 1 and 1000000
361  lastData = data;
362  lastData[KEY_RESPONSE_ID] = lastResponseId;
363  syncData[KEY_RESPONSE_ID] = lastResponseId;
364 
365  return syncData;
366  }
367 }
368 
369 SyncController::SyncController(ISessionManager *sessionManager, QObject *parent)
370  : APIController(sessionManager, parent)
371 {
372  m_freeDiskSpaceThread = new QThread(this);
375 
376  connect(m_freeDiskSpaceThread, &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater);
378 
379  m_freeDiskSpaceThread->start();
380  invokeChecker();
382 }
383 
385 {
386  m_freeDiskSpaceThread->quit();
387  m_freeDiskSpaceThread->wait();
388 }
389 
390 // The function returns the changed data from the server to synchronize with the web client.
391 // Return value is map in JSON format.
392 // Map contain the key:
393 // - "Rid": ID response
394 // Map can contain the keys:
395 // - "full_update": full data update flag
396 // - "torrents": dictionary contains information about torrents.
397 // - "torrents_removed": a list of hashes of removed torrents
398 // - "categories": map of categories info
399 // - "categories_removed": list of removed categories
400 // - "trackers": dictionary contains information about trackers
401 // - "trackers_removed": a list of removed trackers
402 // - "server_state": map contains information about the state of the server
403 // The keys of the 'torrents' dictionary are hashes of torrents.
404 // Each value of the 'torrents' dictionary contains map. The map can contain following keys:
405 // - "name": Torrent name
406 // - "size": Torrent size
407 // - "progress": Torrent progress
408 // - "dlspeed": Torrent download speed
409 // - "upspeed": Torrent upload speed
410 // - "priority": Torrent queue position (-1 if queuing is disabled)
411 // - "num_seeds": Torrent seeds connected to
412 // - "num_complete": Torrent seeds in the swarm
413 // - "num_leechs": Torrent leechers connected to
414 // - "num_incomplete": Torrent leechers in the swarm
415 // - "ratio": Torrent share ratio
416 // - "eta": Torrent ETA
417 // - "state": Torrent state
418 // - "seq_dl": Torrent sequential download state
419 // - "f_l_piece_prio": Torrent first last piece priority state
420 // - "completion_on": Torrent copletion time
421 // - "tracker": Torrent tracker
422 // - "dl_limit": Torrent download limit
423 // - "up_limit": Torrent upload limit
424 // - "downloaded": Amount of data downloaded
425 // - "uploaded": Amount of data uploaded
426 // - "downloaded_session": Amount of data downloaded since program open
427 // - "uploaded_session": Amount of data uploaded since program open
428 // - "amount_left": Amount of data left to download
429 // - "save_path": Torrent save path
430 // - "download_path": Torrent download path
431 // - "completed": Amount of data completed
432 // - "max_ratio": Upload max share ratio
433 // - "max_seeding_time": Upload max seeding time
434 // - "ratio_limit": Upload share ratio limit
435 // - "seeding_time_limit": Upload seeding time limit
436 // - "seen_complete": Indicates the time when the torrent was last seen complete/whole
437 // - "last_activity": Last time when a chunk was downloaded/uploaded
438 // - "total_size": Size including unwanted data
439 // Server state map may contain the following keys:
440 // - "connection_status": connection status
441 // - "dht_nodes": DHT nodes count
442 // - "dl_info_data": bytes downloaded
443 // - "dl_info_speed": download speed
444 // - "dl_rate_limit: download rate limit
445 // - "up_info_data: bytes uploaded
446 // - "up_info_speed: upload speed
447 // - "up_rate_limit: upload speed limit
448 // - "queueing": queue system usage flag
449 // - "refresh_interval": torrents table refresh interval
450 // - "free_space_on_disk": Free space on the default save path
451 // GET param:
452 // - rid (int): last response id
454 {
455  const auto *session = BitTorrent::Session::instance();
456 
457  QVariantMap data;
458 
459  QVariantMap lastResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastResponse")).toMap();
460  QVariantMap lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncMainDataLastAcceptedResponse")).toMap();
461 
462  QVariantHash torrents;
463  QHash<QString, QStringList> trackers;
464  for (const BitTorrent::Torrent *torrent : asConst(session->torrents()))
465  {
466  const BitTorrent::TorrentID torrentID = torrent->id();
467 
468  QVariantMap map = serialize(*torrent);
469  map.remove(KEY_TORRENT_ID);
470 
471  // Calculated last activity time can differ from actual value by up to 10 seconds (this is a libtorrent issue).
472  // So we don't need unnecessary updates of last activity time in response.
473  const auto iterTorrents = lastResponse.find("torrents");
474  if (iterTorrents != lastResponse.end())
475  {
476  const QVariantHash lastResponseTorrents = iterTorrents->toHash();
477  const auto iterID = lastResponseTorrents.find(torrentID.toString());
478 
479  if (iterID != lastResponseTorrents.end())
480  {
481  const QVariantMap torrentData = iterID->toMap();
482  const auto iterLastActivity = torrentData.find(KEY_TORRENT_LAST_ACTIVITY_TIME);
483 
484  if (iterLastActivity != torrentData.end())
485  {
486  const int lastValue = iterLastActivity->toInt();
487  if (qAbs(lastValue - map[KEY_TORRENT_LAST_ACTIVITY_TIME].toInt()) < 15)
488  map[KEY_TORRENT_LAST_ACTIVITY_TIME] = lastValue;
489  }
490  }
491  }
492 
493  for (const BitTorrent::TrackerEntry &tracker : asConst(torrent->trackers()))
494  trackers[tracker.url] << torrentID.toString();
495 
496  torrents[torrentID.toString()] = map;
497  }
498  data["torrents"] = torrents;
499 
500  QVariantHash categories;
501  const QStringList categoriesList = session->categories();
502  for (const auto &categoryName : categoriesList)
503  {
504  const BitTorrent::CategoryOptions categoryOptions = session->categoryOptions(categoryName);
505  QJsonObject category = categoryOptions.toJSON();
506  category.insert(QLatin1String("name"), categoryName);
507  categories[categoryName] = category.toVariantMap();
508  }
509  data["categories"] = categories;
510 
511  QVariantList tags;
512  for (const QString &tag : asConst(session->tags()))
513  tags << tag;
514  data["tags"] = tags;
515 
516  QVariantHash trackersHash;
517  for (auto i = trackers.constBegin(); i != trackers.constEnd(); ++i)
518  {
519  trackersHash[i.key()] = i.value();
520  }
521  data["trackers"] = trackersHash;
522 
523  QVariantMap serverState = getTransferInfo();
525  serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
526  serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
527  serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
528  data["server_state"] = serverState;
529 
530  const int acceptedResponseId {params()["rid"].toInt()};
531  setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
532 
533  sessionManager()->session()->setData(QLatin1String("syncMainDataLastResponse"), lastResponse);
534  sessionManager()->session()->setData(QLatin1String("syncMainDataLastAcceptedResponse"), lastAcceptedResponse);
535 }
536 
537 // GET param:
538 // - hash (string): torrent hash (ID)
539 // - rid (int): last response id
541 {
542  auto lastResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastResponse")).toMap();
543  auto lastAcceptedResponse = sessionManager()->session()->getData(QLatin1String("syncTorrentPeersLastAcceptedResponse")).toMap();
544 
545  const auto id = BitTorrent::TorrentID::fromString(params()["hash"]);
547  if (!torrent)
549 
550  QVariantMap data;
551  QVariantHash peers;
552 
553  const QVector<BitTorrent::PeerInfo> peersList = torrent->peers();
554 
555  bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
556 
557  data[KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS] = resolvePeerCountries;
558 
559  for (const BitTorrent::PeerInfo &pi : peersList)
560  {
561  if (pi.address().ip.isNull()) continue;
562 
563  QVariantMap peer =
564  {
565  {KEY_PEER_IP, pi.address().ip.toString()},
566  {KEY_PEER_PORT, pi.address().port},
567  {KEY_PEER_CLIENT, pi.client()},
568  {KEY_PEER_PROGRESS, pi.progress()},
569  {KEY_PEER_DOWN_SPEED, pi.payloadDownSpeed()},
570  {KEY_PEER_UP_SPEED, pi.payloadUpSpeed()},
571  {KEY_PEER_TOT_DOWN, pi.totalDownload()},
572  {KEY_PEER_TOT_UP, pi.totalUpload()},
573  {KEY_PEER_CONNECTION_TYPE, pi.connectionType()},
574  {KEY_PEER_FLAGS, pi.flags()},
575  {KEY_PEER_FLAGS_DESCRIPTION, pi.flagsDescription()},
576  {KEY_PEER_RELEVANCE, pi.relevance()}
577  };
578 
579  if (torrent->hasMetadata())
580  peer.insert(KEY_PEER_FILES, torrent->info().filesForPiece(pi.downloadingPieceIndex()).join('\n'));
581 
582  if (resolvePeerCountries)
583  {
584  peer[KEY_PEER_COUNTRY_CODE] = pi.country().toLower();
585  peer[KEY_PEER_COUNTRY] = Net::GeoIPManager::CountryName(pi.country());
586  }
587 
588  peers[pi.address().toString()] = peer;
589  }
590  data["peers"] = peers;
591 
592  const int acceptedResponseId {params()["rid"].toInt()};
593  setResult(QJsonObject::fromVariantMap(generateSyncData(acceptedResponseId, data, lastAcceptedResponse, lastResponse)));
594 
595  sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastResponse"), lastResponse);
596  sessionManager()->session()->setData(QLatin1String("syncTorrentPeersLastAcceptedResponse"), lastAcceptedResponse);
597 }
598 
600 {
602  {
603  invokeChecker();
604  m_freeDiskSpaceElapsedTimer.restart();
605  }
606 
607  return m_freeDiskSpace;
608 }
609 
611 {
612  m_freeDiskSpace = freeSpaceSize;
613 }
614 
616 {
617  QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check, Qt::QueuedConnection);
618 }
const StringMap & params() const
const DataMap & data() const
ISessionManager * sessionManager() const
void setResult(const QString &result)
static Session * instance()
Definition: session.cpp:997
Torrent * findTorrent(const TorrentID &id) const
Definition: session.cpp:1742
virtual QVector< PeerInfo > peers() const =0
virtual TorrentInfo info() const =0
virtual bool hasMetadata() const =0
static TorrentID fromString(const QString &hashString)
Definition: infohash.cpp:76
QStringList filesForPiece(int pieceIndex) const
QString toString() const
Definition: digest32.h:85
void checked(qint64 freeSpaceSize)
static QString CountryName(const QString &countryISOCode)
bool resolvePeerCountries() const
static Preferences * instance()
qint64 getFreeDiskSpace()
void invokeChecker() const
QThread * m_freeDiskSpaceThread
FreeDiskSpaceChecker * m_freeDiskSpaceChecker
void freeDiskSpaceSizeUpdated(qint64 freeSpaceSize)
qint64 m_freeDiskSpace
QElapsedTimer m_freeDiskSpaceElapsedTimer
SyncController(ISessionManager *sessionManager, QObject *parent=nullptr)
~SyncController() override
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
QString fromDouble(double n, int precision)
Definition: string.cpp:44
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
QVariantMap generateSyncData(int acceptedResponseId, const QVariantMap &data, QVariantMap &lastAcceptedData, QVariantMap &lastData)
void processList(QVariantList prevData, const QVariantList &data, QVariantList &syncData, QVariantList &removedItems)
void processMap(const QVariantMap &prevData, const QVariantMap &data, QVariantMap &syncData)
void processHash(QVariantHash prevData, const QVariantHash &data, QVariantMap &syncData, QVariantList &removedItems)
QVariantMap serialize(const BitTorrent::Torrent &torrent)
const char KEY_TORRENT_LAST_ACTIVITY_TIME[]
const char KEY_TORRENT_ID[]
QJsonObject toJSON() const
Definition: trackerentry.h:38
virtual QVariant getData(const QString &id) const =0
virtual void setData(const QString &id, const QVariant &data)=0
virtual ISession * session()=0
const char KEY_TRANSFER_DLSPEED[]
const char KEY_TRANSFER_CONNECTION_STATUS[]
const char KEY_TRANSFER_DHT_NODES[]
const char KEY_TRANSFER_UPDATA[]
const char KEY_TRANSFER_DLRATELIMIT[]
const char KEY_TRANSFER_UPSPEED[]
const char KEY_TRANSFER_DLDATA[]
const char KEY_TRANSFER_UPRATELIMIT[]