1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
28 */
29
30 #include "torrentimpl.h"
31
32 #include <algorithm>
33 #include <memory>
34 #include <type_traits>
35
36 #ifdef Q_OS_WIN
37 #include <Windows.h>
38 #endif
39
40 #include <libtorrent/address.hpp>
41 #include <libtorrent/alert_types.hpp>
42 #include <libtorrent/magnet_uri.hpp>
43 #include <libtorrent/session.hpp>
44 #include <libtorrent/storage_defs.hpp>
45 #include <libtorrent/time.hpp>
46 #include <libtorrent/version.hpp>
47
48 #if (LIBTORRENT_VERSION_NUM >= 20000)
49 #include <libtorrent/info_hash.hpp>
50 #endif
51
52 #include <QBitArray>
53 #include <QDebug>
54 #include <QDir>
55 #include <QFile>
56 #include <QStringList>
57 #include <QUrl>
58
59 #include "base/global.h"
60 #include "base/logger.h"
61 #include "base/preferences.h"
62 #include "base/utils/fs.h"
63 #include "base/utils/string.h"
64 #include "common.h"
65 #include "downloadpriority.h"
66 #include "ltqhash.h"
67 #include "ltunderlyingtype.h"
68 #include "peeraddress.h"
69 #include "peerinfo.h"
70 #include "session.h"
71 #include "trackerentry.h"
72
73 using namespace BitTorrent;
74
75 namespace
76 {
toLTDownloadPriorities(const QVector<DownloadPriority> & priorities)77 std::vector<lt::download_priority_t> toLTDownloadPriorities(const QVector<DownloadPriority> &priorities)
78 {
79 std::vector<lt::download_priority_t> out;
80 out.reserve(priorities.size());
81
82 std::transform(priorities.cbegin(), priorities.cend()
83 , std::back_inserter(out), [](const DownloadPriority priority)
84 {
85 return static_cast<lt::download_priority_t>(
86 static_cast<LTUnderlyingType<lt::download_priority_t>>(priority));
87 });
88 return out;
89 }
90
makeNativeAnnouncerEntry(const QString & url,const int tier)91 lt::announce_entry makeNativeAnnouncerEntry(const QString &url, const int tier)
92 {
93 lt::announce_entry entry {url.toStdString()};
94 entry.tier = tier;
95 return entry;
96 }
97
98 #if (LIBTORRENT_VERSION_NUM >= 20000)
fromNativeAnnouncerEntry(const lt::announce_entry & nativeEntry,const lt::info_hash_t & hashes,const QMap<lt::tcp::endpoint,int> & trackerPeerCounts)99 TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry
100 , const lt::info_hash_t &hashes, const QMap<lt::tcp::endpoint, int> &trackerPeerCounts)
101 #else
102 TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry
103 , const QMap<lt::tcp::endpoint, int> &trackerPeerCounts)
104 #endif
105 {
106 TrackerEntry trackerEntry {QString::fromStdString(nativeEntry.url), nativeEntry.tier};
107
108 int numUpdating = 0;
109 int numWorking = 0;
110 int numNotWorking = 0;
111 QString firstTrackerMessage;
112 QString firstErrorMessage;
113 #if (LIBTORRENT_VERSION_NUM >= 20000)
114 const size_t numEndpoints = nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
115 trackerEntry.endpoints.reserve(static_cast<decltype(trackerEntry.endpoints)::size_type>(numEndpoints));
116 for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
117 {
118 for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
119 {
120 if (hashes.has(protocolVersion))
121 {
122 const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
123
124 TrackerEntry::EndpointStats trackerEndpoint;
125 trackerEndpoint.protocolVersion = (protocolVersion == lt::protocol_version::V1) ? 1 : 2;
126 trackerEndpoint.numPeers = trackerPeerCounts.value(endpoint.local_endpoint, -1);
127 trackerEndpoint.numSeeds = infoHash.scrape_complete;
128 trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
129 trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
130
131 if (infoHash.updating)
132 {
133 trackerEndpoint.status = TrackerEntry::Updating;
134 ++numUpdating;
135 }
136 else if (infoHash.fails > 0)
137 {
138 trackerEndpoint.status = TrackerEntry::NotWorking;
139 ++numNotWorking;
140 }
141 else if (nativeEntry.verified)
142 {
143 trackerEndpoint.status = TrackerEntry::Working;
144 ++numWorking;
145 }
146 else
147 {
148 trackerEndpoint.status = TrackerEntry::NotContacted;
149 }
150
151 const QString trackerMessage = QString::fromStdString(infoHash.message);
152 const QString errorMessage = QString::fromLocal8Bit(infoHash.last_error.message().c_str());
153 trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
154
155 trackerEntry.endpoints.append(trackerEndpoint);
156 trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
157 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
158 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
159 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, trackerEndpoint.numDownloaded);
160
161 if (firstTrackerMessage.isEmpty())
162 firstTrackerMessage = trackerMessage;
163 if (firstErrorMessage.isEmpty())
164 firstErrorMessage = errorMessage;
165 }
166 }
167 }
168 #else
169 const int numEndpoints = nativeEntry.endpoints.size();
170 trackerEntry.endpoints.reserve(numEndpoints);
171 for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
172 {
173 TrackerEntry::EndpointStats trackerEndpoint;
174 trackerEndpoint.numPeers = trackerPeerCounts.value(endpoint.local_endpoint, -1);
175 trackerEndpoint.numSeeds = endpoint.scrape_complete;
176 trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
177 trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
178
179 if (endpoint.updating)
180 {
181 trackerEndpoint.status = TrackerEntry::Updating;
182 ++numUpdating;
183 }
184 else if (endpoint.fails > 0)
185 {
186 trackerEndpoint.status = TrackerEntry::NotWorking;
187 ++numNotWorking;
188 }
189 else if (nativeEntry.verified)
190 {
191 trackerEndpoint.status = TrackerEntry::Working;
192 ++numWorking;
193 }
194 else
195 {
196 trackerEndpoint.status = TrackerEntry::NotContacted;
197 }
198
199 const QString trackerMessage = QString::fromStdString(endpoint.message);
200 const QString errorMessage = QString::fromLocal8Bit(endpoint.last_error.message().c_str());
201 trackerEndpoint.message = (!trackerMessage.isEmpty() ? trackerMessage : errorMessage);
202
203 trackerEntry.endpoints.append(trackerEndpoint);
204 trackerEntry.numPeers = std::max(trackerEntry.numPeers, trackerEndpoint.numPeers);
205 trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, trackerEndpoint.numSeeds);
206 trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, trackerEndpoint.numLeeches);
207 trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, trackerEndpoint.numDownloaded);
208
209 if (firstTrackerMessage.isEmpty())
210 firstTrackerMessage = trackerMessage;
211 if (firstErrorMessage.isEmpty())
212 firstErrorMessage = errorMessage;
213 }
214 #endif
215
216 if (numEndpoints > 0)
217 {
218 if (numUpdating > 0)
219 {
220 trackerEntry.status = TrackerEntry::Updating;
221 }
222 else if (numWorking > 0)
223 {
224 trackerEntry.status = TrackerEntry::Working;
225 trackerEntry.message = firstTrackerMessage;
226 }
227 else if (numNotWorking == numEndpoints)
228 {
229 trackerEntry.status = TrackerEntry::NotWorking;
230 trackerEntry.message = (!firstTrackerMessage.isEmpty() ? firstTrackerMessage : firstErrorMessage);
231 }
232 }
233
234 return trackerEntry;
235 }
236
initializeStatus(lt::torrent_status & status,const lt::add_torrent_params & params)237 void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params ¶ms)
238 {
239 status.flags = params.flags;
240 status.active_duration = lt::seconds {params.active_time};
241 status.finished_duration = lt::seconds {params.finished_time};
242 status.seeding_duration = lt::seconds {params.seeding_time};
243 status.num_complete = params.num_complete;
244 status.num_incomplete = params.num_incomplete;
245 status.all_time_download = params.total_downloaded;
246 status.all_time_upload = params.total_uploaded;
247 status.added_time = params.added_time;
248 status.last_seen_complete = params.last_seen_complete;
249 status.last_download = lt::time_point {lt::seconds {params.last_download}};
250 status.last_upload = lt::time_point {lt::seconds {params.last_upload}};
251 status.completed_time = params.completed_time;
252 status.save_path = params.save_path;
253 status.connections_limit = params.max_connections;
254 status.pieces = params.have_pieces;
255 status.verified_pieces = params.verified_pieces;
256 }
257 }
258
259 // TorrentImpl
260
TorrentImpl(Session * session,lt::session * nativeSession,const lt::torrent_handle & nativeHandle,const LoadTorrentParams & params)261 TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
262 , const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
263 : QObject(session)
264 , m_session(session)
265 , m_nativeSession(nativeSession)
266 , m_nativeHandle(nativeHandle)
267 #if (LIBTORRENT_VERSION_NUM >= 20000)
268 , m_infoHash(m_nativeHandle.info_hashes())
269 #else
270 , m_infoHash(m_nativeHandle.info_hash())
271 #endif
272 , m_name(params.name)
273 , m_savePath(Utils::Fs::toNativePath(params.savePath))
274 , m_category(params.category)
275 , m_tags(params.tags)
276 , m_ratioLimit(params.ratioLimit)
277 , m_seedingTimeLimit(params.seedingTimeLimit)
278 , m_operatingMode(params.forced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged)
279 , m_contentLayout(params.contentLayout)
280 , m_hasSeedStatus(params.hasSeedStatus)
281 , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
282 , m_useAutoTMM(params.savePath.isEmpty())
283 , m_isStopped(params.paused)
284 , m_ltAddTorrentParams(params.ltAddTorrentParams)
285 {
286 if (m_useAutoTMM)
287 m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
288
289 if (m_ltAddTorrentParams.ti)
290 {
291 // Initialize it only if torrent is added with metadata.
292 // Otherwise it should be initialized in "Metadata received" handler.
293 m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
294 }
295
296 initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
297 updateState();
298
299 if (hasMetadata())
300 applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
301
302 // TODO: Remove the following upgrade code in v.4.4
303 // == BEGIN UPGRADE CODE ==
304 const QString spath = actualStorageLocation();
305 for (int i = 0; i < filesCount(); ++i)
306 {
307 const QString filepath = filePath(i);
308 // Move "unwanted" files back to their original folder
309 const QString parentRelPath = Utils::Fs::branchPath(filepath);
310 if (QDir(parentRelPath).dirName() == ".unwanted")
311 {
312 const QString oldName = Utils::Fs::fileName(filepath);
313 const QString newRelPath = Utils::Fs::branchPath(parentRelPath);
314 if (newRelPath.isEmpty())
315 renameFile(i, oldName);
316 else
317 renameFile(i, QDir(newRelPath).filePath(oldName));
318
319 // Remove .unwanted directory if empty
320 qDebug() << "Attempting to remove \".unwanted\" folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted");
321 QDir(spath + '/' + newRelPath).rmdir(".unwanted");
322 }
323 }
324 // == END UPGRADE CODE ==
325 }
326
~TorrentImpl()327 TorrentImpl::~TorrentImpl() {}
328
isValid() const329 bool TorrentImpl::isValid() const
330 {
331 return m_nativeHandle.is_valid();
332 }
333
infoHash() const334 InfoHash TorrentImpl::infoHash() const
335 {
336 return m_infoHash;
337 }
338
name() const339 QString TorrentImpl::name() const
340 {
341 if (!m_name.isEmpty())
342 return m_name;
343
344 if (hasMetadata())
345 return m_torrentInfo.name();
346
347 const QString name = QString::fromStdString(m_nativeStatus.name);
348 if (!name.isEmpty())
349 return name;
350
351 return id().toString();
352 }
353
creationDate() const354 QDateTime TorrentImpl::creationDate() const
355 {
356 return m_torrentInfo.creationDate();
357 }
358
creator() const359 QString TorrentImpl::creator() const
360 {
361 return m_torrentInfo.creator();
362 }
363
comment() const364 QString TorrentImpl::comment() const
365 {
366 return m_torrentInfo.comment();
367 }
368
isPrivate() const369 bool TorrentImpl::isPrivate() const
370 {
371 return m_torrentInfo.isPrivate();
372 }
373
totalSize() const374 qlonglong TorrentImpl::totalSize() const
375 {
376 return m_torrentInfo.totalSize();
377 }
378
379 // size without the "don't download" files
wantedSize() const380 qlonglong TorrentImpl::wantedSize() const
381 {
382 return m_nativeStatus.total_wanted;
383 }
384
completedSize() const385 qlonglong TorrentImpl::completedSize() const
386 {
387 return m_nativeStatus.total_wanted_done;
388 }
389
pieceLength() const390 qlonglong TorrentImpl::pieceLength() const
391 {
392 return m_torrentInfo.pieceLength();
393 }
394
wastedSize() const395 qlonglong TorrentImpl::wastedSize() const
396 {
397 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
398 }
399
currentTracker() const400 QString TorrentImpl::currentTracker() const
401 {
402 return QString::fromStdString(m_nativeStatus.current_tracker);
403 }
404
savePath(bool actual) const405 QString TorrentImpl::savePath(bool actual) const
406 {
407 if (actual)
408 return Utils::Fs::toUniformPath(actualStorageLocation());
409 else
410 return Utils::Fs::toUniformPath(m_savePath);
411 }
412
rootPath(bool actual) const413 QString TorrentImpl::rootPath(bool actual) const
414 {
415 if (!hasMetadata())
416 return {};
417
418 const QString firstFilePath = filePath(0);
419 const int slashIndex = firstFilePath.indexOf('/');
420 if (slashIndex >= 0)
421 return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex));
422 else
423 return QDir(savePath(actual)).absoluteFilePath(firstFilePath);
424 }
425
contentPath(const bool actual) const426 QString TorrentImpl::contentPath(const bool actual) const
427 {
428 if (!hasMetadata())
429 return {};
430
431 if (filesCount() == 1)
432 return QDir(savePath(actual)).absoluteFilePath(filePath(0));
433
434 if (m_torrentInfo.hasRootFolder())
435 return rootPath(actual);
436
437 return savePath(actual);
438 }
439
isAutoTMMEnabled() const440 bool TorrentImpl::isAutoTMMEnabled() const
441 {
442 return m_useAutoTMM;
443 }
444
setAutoTMMEnabled(bool enabled)445 void TorrentImpl::setAutoTMMEnabled(bool enabled)
446 {
447 if (m_useAutoTMM == enabled) return;
448
449 m_useAutoTMM = enabled;
450 m_session->handleTorrentNeedSaveResumeData(this);
451 m_session->handleTorrentSavingModeChanged(this);
452
453 if (m_useAutoTMM)
454 move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
455 }
456
actualStorageLocation() const457 QString TorrentImpl::actualStorageLocation() const
458 {
459 return QString::fromStdString(m_nativeStatus.save_path);
460 }
461
setAutoManaged(const bool enable)462 void TorrentImpl::setAutoManaged(const bool enable)
463 {
464 if (enable)
465 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
466 else
467 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
468 }
469
trackers() const470 QVector<TrackerEntry> TorrentImpl::trackers() const
471 {
472 const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
473
474 QVector<TrackerEntry> entries;
475 entries.reserve(static_cast<decltype(entries)::size_type>(nativeTrackers.size()));
476
477 for (const lt::announce_entry &tracker : nativeTrackers)
478 {
479 const QString trackerURL = QString::fromStdString(tracker.url);
480 #if (LIBTORRENT_VERSION_NUM >= 20000)
481 entries << fromNativeAnnouncerEntry(tracker, m_nativeHandle.info_hashes(), m_trackerPeerCounts[trackerURL]);
482 #else
483 entries << fromNativeAnnouncerEntry(tracker, m_trackerPeerCounts[trackerURL]);
484 #endif
485 }
486
487 return entries;
488 }
489
addTrackers(const QVector<TrackerEntry> & trackers)490 void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
491 {
492 QSet<TrackerEntry> currentTrackers;
493 for (const lt::announce_entry &entry : m_nativeHandle.trackers())
494 currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
495
496 QVector<TrackerEntry> newTrackers;
497 newTrackers.reserve(trackers.size());
498
499 for (const TrackerEntry &tracker : trackers)
500 {
501 if (!currentTrackers.contains(tracker))
502 {
503 m_nativeHandle.add_tracker(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
504 newTrackers << tracker;
505 }
506 }
507
508 if (!newTrackers.isEmpty())
509 {
510 m_session->handleTorrentNeedSaveResumeData(this);
511 m_session->handleTorrentTrackersAdded(this, newTrackers);
512 }
513 }
514
replaceTrackers(const QVector<TrackerEntry> & trackers)515 void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
516 {
517 QVector<TrackerEntry> currentTrackers = this->trackers();
518
519 QVector<TrackerEntry> newTrackers;
520 newTrackers.reserve(trackers.size());
521
522 std::vector<lt::announce_entry> nativeTrackers;
523 nativeTrackers.reserve(trackers.size());
524
525 for (const TrackerEntry &tracker : trackers)
526 {
527 nativeTrackers.emplace_back(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
528
529 if (!currentTrackers.removeOne(tracker))
530 newTrackers << tracker;
531 }
532
533 m_nativeHandle.replace_trackers(nativeTrackers);
534
535 m_session->handleTorrentNeedSaveResumeData(this);
536
537 if (newTrackers.isEmpty() && currentTrackers.isEmpty())
538 {
539 // when existing tracker reorders
540 m_session->handleTorrentTrackersChanged(this);
541 }
542 else
543 {
544 if (!currentTrackers.isEmpty())
545 m_session->handleTorrentTrackersRemoved(this, currentTrackers);
546
547 if (!newTrackers.isEmpty())
548 m_session->handleTorrentTrackersAdded(this, newTrackers);
549
550 // Clear the peer list if it's a private torrent since
551 // we do not want to keep connecting with peers from old tracker.
552 if (isPrivate())
553 clearPeers();
554 }
555 }
556
urlSeeds() const557 QVector<QUrl> TorrentImpl::urlSeeds() const
558 {
559 const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
560
561 QVector<QUrl> urlSeeds;
562 urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
563
564 for (const std::string &urlSeed : currentSeeds)
565 urlSeeds.append(QString::fromStdString(urlSeed));
566
567 return urlSeeds;
568 }
569
addUrlSeeds(const QVector<QUrl> & urlSeeds)570 void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
571 {
572 const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
573
574 QVector<QUrl> addedUrlSeeds;
575 addedUrlSeeds.reserve(urlSeeds.size());
576
577 for (const QUrl &url : urlSeeds)
578 {
579 const std::string nativeUrl = url.toString().toStdString();
580 if (currentSeeds.find(nativeUrl) == currentSeeds.end())
581 {
582 m_nativeHandle.add_url_seed(nativeUrl);
583 addedUrlSeeds << url;
584 }
585 }
586
587 if (!addedUrlSeeds.isEmpty())
588 {
589 m_session->handleTorrentNeedSaveResumeData(this);
590 m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
591 }
592 }
593
removeUrlSeeds(const QVector<QUrl> & urlSeeds)594 void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
595 {
596 const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
597
598 QVector<QUrl> removedUrlSeeds;
599 removedUrlSeeds.reserve(urlSeeds.size());
600
601 for (const QUrl &url : urlSeeds)
602 {
603 const std::string nativeUrl = url.toString().toStdString();
604 if (currentSeeds.find(nativeUrl) != currentSeeds.end())
605 {
606 m_nativeHandle.remove_url_seed(nativeUrl);
607 removedUrlSeeds << url;
608 }
609 }
610
611 if (!removedUrlSeeds.isEmpty())
612 {
613 m_session->handleTorrentNeedSaveResumeData(this);
614 m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
615 }
616 }
617
clearPeers()618 void TorrentImpl::clearPeers()
619 {
620 m_nativeHandle.clear_peers();
621 }
622
connectPeer(const PeerAddress & peerAddress)623 bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
624 {
625 lt::error_code ec;
626 const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec);
627 if (ec) return false;
628
629 const lt::tcp::endpoint endpoint(addr, peerAddress.port);
630 try
631 {
632 m_nativeHandle.connect_peer(endpoint);
633 }
634 catch (const lt::system_error &err)
635 {
636 LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
637 .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
638 return false;
639 }
640
641 LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name()));
642 return true;
643 }
644
needSaveResumeData() const645 bool TorrentImpl::needSaveResumeData() const
646 {
647 return m_nativeHandle.need_save_resume_data();
648 }
649
saveResumeData()650 void TorrentImpl::saveResumeData()
651 {
652 m_nativeHandle.save_resume_data();
653 m_session->handleTorrentSaveResumeDataRequested(this);
654 }
655
filesCount() const656 int TorrentImpl::filesCount() const
657 {
658 return m_torrentInfo.filesCount();
659 }
660
piecesCount() const661 int TorrentImpl::piecesCount() const
662 {
663 return m_torrentInfo.piecesCount();
664 }
665
piecesHave() const666 int TorrentImpl::piecesHave() const
667 {
668 return m_nativeStatus.num_pieces;
669 }
670
progress() const671 qreal TorrentImpl::progress() const
672 {
673 if (isChecking())
674 return m_nativeStatus.progress;
675
676 if (m_nativeStatus.total_wanted == 0)
677 return 0.;
678
679 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
680 return 1.;
681
682 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
683 Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
684 return progress;
685 }
686
category() const687 QString TorrentImpl::category() const
688 {
689 return m_category;
690 }
691
belongsToCategory(const QString & category) const692 bool TorrentImpl::belongsToCategory(const QString &category) const
693 {
694 if (m_category.isEmpty()) return category.isEmpty();
695 if (!Session::isValidCategoryName(category)) return false;
696
697 if (m_category == category) return true;
698
699 if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/'))
700 return true;
701
702 return false;
703 }
704
tags() const705 QSet<QString> TorrentImpl::tags() const
706 {
707 return m_tags;
708 }
709
hasTag(const QString & tag) const710 bool TorrentImpl::hasTag(const QString &tag) const
711 {
712 return m_tags.contains(tag);
713 }
714
addTag(const QString & tag)715 bool TorrentImpl::addTag(const QString &tag)
716 {
717 if (!Session::isValidTag(tag))
718 return false;
719
720 if (!hasTag(tag))
721 {
722 if (!m_session->hasTag(tag))
723 if (!m_session->addTag(tag))
724 return false;
725 m_tags.insert(tag);
726 m_session->handleTorrentNeedSaveResumeData(this);
727 m_session->handleTorrentTagAdded(this, tag);
728 return true;
729 }
730 return false;
731 }
732
removeTag(const QString & tag)733 bool TorrentImpl::removeTag(const QString &tag)
734 {
735 if (m_tags.remove(tag))
736 {
737 m_session->handleTorrentNeedSaveResumeData(this);
738 m_session->handleTorrentTagRemoved(this, tag);
739 return true;
740 }
741 return false;
742 }
743
removeAllTags()744 void TorrentImpl::removeAllTags()
745 {
746 for (const QString &tag : asConst(tags()))
747 removeTag(tag);
748 }
749
addedTime() const750 QDateTime TorrentImpl::addedTime() const
751 {
752 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
753 }
754
ratioLimit() const755 qreal TorrentImpl::ratioLimit() const
756 {
757 return m_ratioLimit;
758 }
759
seedingTimeLimit() const760 int TorrentImpl::seedingTimeLimit() const
761 {
762 return m_seedingTimeLimit;
763 }
764
filePath(int index) const765 QString TorrentImpl::filePath(int index) const
766 {
767 return m_torrentInfo.filePath(index);
768 }
769
fileName(int index) const770 QString TorrentImpl::fileName(int index) const
771 {
772 if (!hasMetadata()) return {};
773 return Utils::Fs::fileName(filePath(index));
774 }
775
fileSize(int index) const776 qlonglong TorrentImpl::fileSize(int index) const
777 {
778 return m_torrentInfo.fileSize(index);
779 }
780
781 // Return a list of absolute paths corresponding
782 // to all files in a torrent
absoluteFilePaths() const783 QStringList TorrentImpl::absoluteFilePaths() const
784 {
785 if (!hasMetadata()) return {};
786
787 const QDir saveDir(savePath(true));
788 QStringList res;
789 for (int i = 0; i < filesCount(); ++i)
790 res << Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i)));
791 return res;
792 }
793
filePriorities() const794 QVector<DownloadPriority> TorrentImpl::filePriorities() const
795 {
796 const std::vector<lt::download_priority_t> fp = m_nativeHandle.get_file_priorities();
797
798 QVector<DownloadPriority> ret;
799 std::transform(fp.cbegin(), fp.cend(), std::back_inserter(ret), [](lt::download_priority_t priority)
800 {
801 return static_cast<DownloadPriority>(static_cast<LTUnderlyingType<lt::download_priority_t>>(priority));
802 });
803 return ret;
804 }
805
info() const806 TorrentInfo TorrentImpl::info() const
807 {
808 return m_torrentInfo;
809 }
810
isPaused() const811 bool TorrentImpl::isPaused() const
812 {
813 return m_isStopped;
814 }
815
isQueued() const816 bool TorrentImpl::isQueued() const
817 {
818 // Torrent is Queued if it isn't in Paused state but paused internally
819 return (!isPaused()
820 && (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
821 && (m_nativeStatus.flags & lt::torrent_flags::paused));
822 }
823
isChecking() const824 bool TorrentImpl::isChecking() const
825 {
826 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
827 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
828 }
829
isDownloading() const830 bool TorrentImpl::isDownloading() const
831 {
832 return m_state == TorrentState::Downloading
833 || m_state == TorrentState::DownloadingMetadata
834 || m_state == TorrentState::StalledDownloading
835 || m_state == TorrentState::CheckingDownloading
836 || m_state == TorrentState::PausedDownloading
837 || m_state == TorrentState::QueuedDownloading
838 || m_state == TorrentState::ForcedDownloading;
839 }
840
isUploading() const841 bool TorrentImpl::isUploading() const
842 {
843 return m_state == TorrentState::Uploading
844 || m_state == TorrentState::StalledUploading
845 || m_state == TorrentState::CheckingUploading
846 || m_state == TorrentState::QueuedUploading
847 || m_state == TorrentState::ForcedUploading;
848 }
849
isCompleted() const850 bool TorrentImpl::isCompleted() const
851 {
852 return m_state == TorrentState::Uploading
853 || m_state == TorrentState::StalledUploading
854 || m_state == TorrentState::CheckingUploading
855 || m_state == TorrentState::PausedUploading
856 || m_state == TorrentState::QueuedUploading
857 || m_state == TorrentState::ForcedUploading;
858 }
859
isActive() const860 bool TorrentImpl::isActive() const
861 {
862 if (m_state == TorrentState::StalledDownloading)
863 return (uploadPayloadRate() > 0);
864
865 return m_state == TorrentState::DownloadingMetadata
866 || m_state == TorrentState::Downloading
867 || m_state == TorrentState::ForcedDownloading
868 || m_state == TorrentState::Uploading
869 || m_state == TorrentState::ForcedUploading
870 || m_state == TorrentState::Moving;
871 }
872
isInactive() const873 bool TorrentImpl::isInactive() const
874 {
875 return !isActive();
876 }
877
isErrored() const878 bool TorrentImpl::isErrored() const
879 {
880 return m_state == TorrentState::MissingFiles
881 || m_state == TorrentState::Error;
882 }
883
isSeed() const884 bool TorrentImpl::isSeed() const
885 {
886 return ((m_nativeStatus.state == lt::torrent_status::finished)
887 || (m_nativeStatus.state == lt::torrent_status::seeding));
888 }
889
isForced() const890 bool TorrentImpl::isForced() const
891 {
892 return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
893 }
894
isSequentialDownload() const895 bool TorrentImpl::isSequentialDownload() const
896 {
897 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::sequential_download);
898 }
899
hasFirstLastPiecePriority() const900 bool TorrentImpl::hasFirstLastPiecePriority() const
901 {
902 return m_hasFirstLastPiecePriority;
903 }
904
state() const905 TorrentState TorrentImpl::state() const
906 {
907 return m_state;
908 }
909
updateState()910 void TorrentImpl::updateState()
911 {
912 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
913 {
914 m_state = TorrentState::CheckingResumeData;
915 }
916 else if (isMoveInProgress())
917 {
918 m_state = TorrentState::Moving;
919 }
920 else if (hasMissingFiles())
921 {
922 m_state = TorrentState::MissingFiles;
923 }
924 else if (hasError())
925 {
926 m_state = TorrentState::Error;
927 }
928 else if (!hasMetadata())
929 {
930 if (isPaused())
931 m_state = TorrentState::PausedDownloading;
932 else if (m_session->isQueueingSystemEnabled() && isQueued())
933 m_state = TorrentState::QueuedDownloading;
934 else
935 m_state = TorrentState::DownloadingMetadata;
936 }
937 else if ((m_nativeStatus.state == lt::torrent_status::checking_files)
938 && (!isPaused() || (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
939 || !(m_nativeStatus.flags & lt::torrent_flags::paused)))
940 {
941 // If the torrent is not just in the "checking" state, but is being actually checked
942 m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
943 }
944 else if (isSeed())
945 {
946 if (isPaused())
947 m_state = TorrentState::PausedUploading;
948 else if (m_session->isQueueingSystemEnabled() && isQueued())
949 m_state = TorrentState::QueuedUploading;
950 else if (isForced())
951 m_state = TorrentState::ForcedUploading;
952 else if (m_nativeStatus.upload_payload_rate > 0)
953 m_state = TorrentState::Uploading;
954 else
955 m_state = TorrentState::StalledUploading;
956 }
957 else
958 {
959 if (isPaused())
960 m_state = TorrentState::PausedDownloading;
961 else if (m_session->isQueueingSystemEnabled() && isQueued())
962 m_state = TorrentState::QueuedDownloading;
963 else if (isForced())
964 m_state = TorrentState::ForcedDownloading;
965 else if (m_nativeStatus.download_payload_rate > 0)
966 m_state = TorrentState::Downloading;
967 else
968 m_state = TorrentState::StalledDownloading;
969 }
970 }
971
hasMetadata() const972 bool TorrentImpl::hasMetadata() const
973 {
974 return m_torrentInfo.isValid();
975 }
976
hasMissingFiles() const977 bool TorrentImpl::hasMissingFiles() const
978 {
979 return m_hasMissingFiles;
980 }
981
hasError() const982 bool TorrentImpl::hasError() const
983 {
984 return (m_nativeStatus.errc || (m_nativeStatus.flags & lt::torrent_flags::upload_mode));
985 }
986
hasFilteredPieces() const987 bool TorrentImpl::hasFilteredPieces() const
988 {
989 const std::vector<lt::download_priority_t> pp = m_nativeHandle.get_piece_priorities();
990 return std::any_of(pp.cbegin(), pp.cend(), [](const lt::download_priority_t priority)
991 {
992 return (priority == lt::download_priority_t {0});
993 });
994 }
995
queuePosition() const996 int TorrentImpl::queuePosition() const
997 {
998 return static_cast<int>(m_nativeStatus.queue_position);
999 }
1000
error() const1001 QString TorrentImpl::error() const
1002 {
1003 if (m_nativeStatus.errc)
1004 return QString::fromStdString(m_nativeStatus.errc.message());
1005
1006 if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
1007 {
1008 const QString writeErrorStr = tr("Couldn't write to file.");
1009 const QString uploadModeStr = tr("Torrent is currently in \"upload only\" mode.");
1010 const QString errorMessage = QString::fromLocal8Bit(m_lastFileError.error.message().c_str());
1011
1012 return writeErrorStr + QLatin1Char(' ') + errorMessage + QLatin1String(". ") + uploadModeStr;
1013 }
1014
1015 return {};
1016 }
1017
totalDownload() const1018 qlonglong TorrentImpl::totalDownload() const
1019 {
1020 return m_nativeStatus.all_time_download;
1021 }
1022
totalUpload() const1023 qlonglong TorrentImpl::totalUpload() const
1024 {
1025 return m_nativeStatus.all_time_upload;
1026 }
1027
activeTime() const1028 qlonglong TorrentImpl::activeTime() const
1029 {
1030 return lt::total_seconds(m_nativeStatus.active_duration);
1031 }
1032
finishedTime() const1033 qlonglong TorrentImpl::finishedTime() const
1034 {
1035 return lt::total_seconds(m_nativeStatus.finished_duration);
1036 }
1037
seedingTime() const1038 qlonglong TorrentImpl::seedingTime() const
1039 {
1040 return lt::total_seconds(m_nativeStatus.seeding_duration);
1041 }
1042
eta() const1043 qlonglong TorrentImpl::eta() const
1044 {
1045 if (isPaused()) return MAX_ETA;
1046
1047 const SpeedSampleAvg speedAverage = m_speedMonitor.average();
1048
1049 if (isSeed())
1050 {
1051 const qreal maxRatioValue = maxRatio();
1052 const int maxSeedingTimeValue = maxSeedingTime();
1053 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
1054
1055 qlonglong ratioEta = MAX_ETA;
1056
1057 if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
1058 {
1059
1060 qlonglong realDL = totalDownload();
1061 if (realDL <= 0)
1062 realDL = wantedSize();
1063
1064 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1065 }
1066
1067 qlonglong seedingTimeEta = MAX_ETA;
1068
1069 if (maxSeedingTimeValue >= 0)
1070 {
1071 seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime();
1072 if (seedingTimeEta < 0)
1073 seedingTimeEta = 0;
1074 }
1075
1076 return qMin(ratioEta, seedingTimeEta);
1077 }
1078
1079 if (!speedAverage.download) return MAX_ETA;
1080
1081 return (wantedSize() - completedSize()) / speedAverage.download;
1082 }
1083
filesProgress() const1084 QVector<qreal> TorrentImpl::filesProgress() const
1085 {
1086 if (!hasMetadata())
1087 return {};
1088
1089 std::vector<int64_t> fp;
1090 m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
1091
1092 const int count = static_cast<int>(fp.size());
1093 QVector<qreal> result;
1094 result.reserve(count);
1095 for (int i = 0; i < count; ++i)
1096 {
1097 const qlonglong size = fileSize(i);
1098 if ((size <= 0) || (fp[i] == size))
1099 result << 1;
1100 else
1101 result << (fp[i] / static_cast<qreal>(size));
1102 }
1103
1104 return result;
1105 }
1106
seedsCount() const1107 int TorrentImpl::seedsCount() const
1108 {
1109 return m_nativeStatus.num_seeds;
1110 }
1111
peersCount() const1112 int TorrentImpl::peersCount() const
1113 {
1114 return m_nativeStatus.num_peers;
1115 }
1116
leechsCount() const1117 int TorrentImpl::leechsCount() const
1118 {
1119 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1120 }
1121
totalSeedsCount() const1122 int TorrentImpl::totalSeedsCount() const
1123 {
1124 return (m_nativeStatus.num_complete > 0) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1125 }
1126
totalPeersCount() const1127 int TorrentImpl::totalPeersCount() const
1128 {
1129 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1130 return (peers > 0) ? peers : m_nativeStatus.list_peers;
1131 }
1132
totalLeechersCount() const1133 int TorrentImpl::totalLeechersCount() const
1134 {
1135 return (m_nativeStatus.num_incomplete > 0) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1136 }
1137
completeCount() const1138 int TorrentImpl::completeCount() const
1139 {
1140 // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
1141 return m_nativeStatus.num_complete;
1142 }
1143
incompleteCount() const1144 int TorrentImpl::incompleteCount() const
1145 {
1146 // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
1147 return m_nativeStatus.num_incomplete;
1148 }
1149
lastSeenComplete() const1150 QDateTime TorrentImpl::lastSeenComplete() const
1151 {
1152 if (m_nativeStatus.last_seen_complete > 0)
1153 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1154 else
1155 return {};
1156 }
1157
completedTime() const1158 QDateTime TorrentImpl::completedTime() const
1159 {
1160 if (m_nativeStatus.completed_time > 0)
1161 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1162 else
1163 return {};
1164 }
1165
timeSinceUpload() const1166 qlonglong TorrentImpl::timeSinceUpload() const
1167 {
1168 if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
1169 return -1;
1170 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1171 }
1172
timeSinceDownload() const1173 qlonglong TorrentImpl::timeSinceDownload() const
1174 {
1175 if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
1176 return -1;
1177 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1178 }
1179
timeSinceActivity() const1180 qlonglong TorrentImpl::timeSinceActivity() const
1181 {
1182 const qlonglong upTime = timeSinceUpload();
1183 const qlonglong downTime = timeSinceDownload();
1184 return ((upTime < 0) != (downTime < 0))
1185 ? std::max(upTime, downTime)
1186 : std::min(upTime, downTime);
1187 }
1188
downloadLimit() const1189 int TorrentImpl::downloadLimit() const
1190 {
1191 return m_nativeHandle.download_limit();
1192 }
1193
uploadLimit() const1194 int TorrentImpl::uploadLimit() const
1195 {
1196 return m_nativeHandle.upload_limit();
1197 }
1198
superSeeding() const1199 bool TorrentImpl::superSeeding() const
1200 {
1201 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::super_seeding);
1202 }
1203
isDHTDisabled() const1204 bool TorrentImpl::isDHTDisabled() const
1205 {
1206 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_dht);
1207 }
1208
isPEXDisabled() const1209 bool TorrentImpl::isPEXDisabled() const
1210 {
1211 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_pex);
1212 }
1213
isLSDDisabled() const1214 bool TorrentImpl::isLSDDisabled() const
1215 {
1216 return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
1217 }
1218
peers() const1219 QVector<PeerInfo> TorrentImpl::peers() const
1220 {
1221 std::vector<lt::peer_info> nativePeers;
1222 m_nativeHandle.get_peer_info(nativePeers);
1223
1224 QVector<PeerInfo> peers;
1225 peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
1226
1227 for (const lt::peer_info &peer : nativePeers)
1228 peers << PeerInfo(this, peer);
1229
1230 return peers;
1231 }
1232
pieces() const1233 QBitArray TorrentImpl::pieces() const
1234 {
1235 QBitArray result(m_nativeStatus.pieces.size());
1236 for (int i = 0; i < result.size(); ++i)
1237 {
1238 if (m_nativeStatus.pieces[lt::piece_index_t {i}])
1239 result.setBit(i, true);
1240 }
1241 return result;
1242 }
1243
downloadingPieces() const1244 QBitArray TorrentImpl::downloadingPieces() const
1245 {
1246 QBitArray result(piecesCount());
1247
1248 std::vector<lt::partial_piece_info> queue;
1249 m_nativeHandle.get_download_queue(queue);
1250
1251 for (const lt::partial_piece_info &info : queue)
1252 result.setBit(static_cast<LTUnderlyingType<lt::piece_index_t>>(info.piece_index));
1253
1254 return result;
1255 }
1256
pieceAvailability() const1257 QVector<int> TorrentImpl::pieceAvailability() const
1258 {
1259 std::vector<int> avail;
1260 m_nativeHandle.piece_availability(avail);
1261
1262 return Vector::fromStdVector(avail);
1263 }
1264
distributedCopies() const1265 qreal TorrentImpl::distributedCopies() const
1266 {
1267 return m_nativeStatus.distributed_copies;
1268 }
1269
maxRatio() const1270 qreal TorrentImpl::maxRatio() const
1271 {
1272 if (m_ratioLimit == USE_GLOBAL_RATIO)
1273 return m_session->globalMaxRatio();
1274
1275 return m_ratioLimit;
1276 }
1277
maxSeedingTime() const1278 int TorrentImpl::maxSeedingTime() const
1279 {
1280 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1281 return m_session->globalMaxSeedingMinutes();
1282
1283 return m_seedingTimeLimit;
1284 }
1285
realRatio() const1286 qreal TorrentImpl::realRatio() const
1287 {
1288 const int64_t upload = m_nativeStatus.all_time_upload;
1289 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1290 const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01))
1291 ? m_nativeStatus.total_done
1292 : m_nativeStatus.all_time_download;
1293
1294 if (download == 0)
1295 return (upload == 0) ? 0 : MAX_RATIO;
1296
1297 const qreal ratio = upload / static_cast<qreal>(download);
1298 Q_ASSERT(ratio >= 0);
1299 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1300 }
1301
uploadPayloadRate() const1302 int TorrentImpl::uploadPayloadRate() const
1303 {
1304 return m_nativeStatus.upload_payload_rate;
1305 }
1306
downloadPayloadRate() const1307 int TorrentImpl::downloadPayloadRate() const
1308 {
1309 return m_nativeStatus.download_payload_rate;
1310 }
1311
totalPayloadUpload() const1312 qlonglong TorrentImpl::totalPayloadUpload() const
1313 {
1314 return m_nativeStatus.total_payload_upload;
1315 }
1316
totalPayloadDownload() const1317 qlonglong TorrentImpl::totalPayloadDownload() const
1318 {
1319 return m_nativeStatus.total_payload_download;
1320 }
1321
connectionsCount() const1322 int TorrentImpl::connectionsCount() const
1323 {
1324 return m_nativeStatus.num_connections;
1325 }
1326
connectionsLimit() const1327 int TorrentImpl::connectionsLimit() const
1328 {
1329 return m_nativeStatus.connections_limit;
1330 }
1331
nextAnnounce() const1332 qlonglong TorrentImpl::nextAnnounce() const
1333 {
1334 return lt::total_seconds(m_nativeStatus.next_announce);
1335 }
1336
setName(const QString & name)1337 void TorrentImpl::setName(const QString &name)
1338 {
1339 if (m_name != name)
1340 {
1341 m_name = name;
1342 m_session->handleTorrentNeedSaveResumeData(this);
1343 m_session->handleTorrentNameChanged(this);
1344 }
1345 }
1346
setCategory(const QString & category)1347 bool TorrentImpl::setCategory(const QString &category)
1348 {
1349 if (m_category != category)
1350 {
1351 if (!category.isEmpty() && !m_session->categories().contains(category))
1352 return false;
1353
1354 const QString oldCategory = m_category;
1355 m_category = category;
1356 m_session->handleTorrentNeedSaveResumeData(this);
1357 m_session->handleTorrentCategoryChanged(this, oldCategory);
1358
1359 if (m_useAutoTMM)
1360 {
1361 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1362 move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
1363 else
1364 setAutoTMMEnabled(false);
1365 }
1366 }
1367
1368 return true;
1369 }
1370
move(QString path)1371 void TorrentImpl::move(QString path)
1372 {
1373 if (m_useAutoTMM)
1374 {
1375 m_useAutoTMM = false;
1376 m_session->handleTorrentNeedSaveResumeData(this);
1377 m_session->handleTorrentSavingModeChanged(this);
1378 }
1379
1380 path = Utils::Fs::toUniformPath(path.trimmed());
1381 if (path.isEmpty())
1382 path = m_session->defaultSavePath();
1383 if (!path.endsWith('/'))
1384 path += '/';
1385
1386 move_impl(path, MoveStorageMode::KeepExistingFiles);
1387 }
1388
move_impl(QString path,const MoveStorageMode mode)1389 void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
1390 {
1391 if (path == savePath()) return;
1392
1393 path = Utils::Fs::toNativePath(path);
1394 if (!useTempPath())
1395 {
1396 moveStorage(path, mode);
1397 }
1398 else
1399 {
1400 m_savePath = path;
1401 m_session->handleTorrentNeedSaveResumeData(this);
1402 m_session->handleTorrentSavePathChanged(this);
1403 }
1404 }
1405
forceReannounce(int index)1406 void TorrentImpl::forceReannounce(int index)
1407 {
1408 m_nativeHandle.force_reannounce(0, index);
1409 }
1410
forceDHTAnnounce()1411 void TorrentImpl::forceDHTAnnounce()
1412 {
1413 m_nativeHandle.force_dht_announce();
1414 }
1415
forceRecheck()1416 void TorrentImpl::forceRecheck()
1417 {
1418 if (!hasMetadata()) return;
1419
1420 m_nativeHandle.force_recheck();
1421 m_hasMissingFiles = false;
1422 m_unchecked = false;
1423
1424 if (isPaused())
1425 {
1426 // When "force recheck" is applied on paused torrent, we temporarily resume it
1427 // (really we just allow libtorrent to resume it by enabling auto management for it).
1428 m_nativeHandle.set_flags(lt::torrent_flags::stop_when_ready | lt::torrent_flags::auto_managed);
1429 }
1430 }
1431
setSequentialDownload(const bool enable)1432 void TorrentImpl::setSequentialDownload(const bool enable)
1433 {
1434 if (enable)
1435 {
1436 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1437 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1438 }
1439 else
1440 {
1441 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1442 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1443 }
1444
1445 m_session->handleTorrentNeedSaveResumeData(this);
1446 }
1447
setFirstLastPiecePriority(const bool enabled)1448 void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
1449 {
1450 if (m_hasFirstLastPiecePriority == enabled)
1451 return;
1452
1453 m_hasFirstLastPiecePriority = enabled;
1454 if (hasMetadata())
1455 applyFirstLastPiecePriority(enabled);
1456
1457 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1458 .arg((enabled ? tr("On") : tr("Off")), name()));
1459
1460 m_session->handleTorrentNeedSaveResumeData(this);
1461 }
1462
applyFirstLastPiecePriority(const bool enabled,const QVector<DownloadPriority> & updatedFilePrio)1463 void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
1464 {
1465 Q_ASSERT(hasMetadata());
1466
1467 // Download first and last pieces first for every file in the torrent
1468
1469 const std::vector<lt::download_priority_t> filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio)
1470 : nativeHandle().get_file_priorities();
1471 std::vector<lt::download_priority_t> piecePriorities = nativeHandle().get_piece_priorities();
1472
1473 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1474 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1475 for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index)
1476 {
1477 const lt::download_priority_t filePrio = filePriorities[index];
1478 if (filePrio <= lt::download_priority_t {0})
1479 continue;
1480
1481 // Determine the priority to set
1482 const lt::download_priority_t newPrio = enabled ? lt::download_priority_t {7} : filePrio;
1483 const TorrentInfo::PieceRange extremities = info().filePieces(index);
1484
1485 // worst case: AVI index = 1% of total file size (at the end of the file)
1486 const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
1487 for (int i = 0; i < nNumPieces; ++i)
1488 {
1489 piecePriorities[extremities.first() + i] = newPrio;
1490 piecePriorities[extremities.last() - i] = newPrio;
1491 }
1492 }
1493
1494 m_nativeHandle.prioritize_pieces(piecePriorities);
1495 }
1496
fileSearchFinished(const QString & savePath,const QStringList & fileNames)1497 void TorrentImpl::fileSearchFinished(const QString &savePath, const QStringList &fileNames)
1498 {
1499 endReceivedMetadataHandling(savePath, fileNames);
1500 }
1501
endReceivedMetadataHandling(const QString & savePath,const QStringList & fileNames)1502 void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames)
1503 {
1504 lt::add_torrent_params &p = m_ltAddTorrentParams;
1505
1506 p.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
1507 for (int i = 0; i < fileNames.size(); ++i)
1508 p.renamed_files[lt::file_index_t {i}] = fileNames[i].toStdString();
1509 p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
1510
1511 reload();
1512
1513 // If first/last piece priority was specified when adding this torrent,
1514 // we should apply it now that we have metadata:
1515 if (m_hasFirstLastPiecePriority)
1516 applyFirstLastPiecePriority(true);
1517
1518 m_maintenanceJob = MaintenanceJob::None;
1519 updateStatus();
1520
1521 m_session->handleTorrentMetadataReceived(this);
1522 }
1523
reload()1524 void TorrentImpl::reload()
1525 {
1526 const auto queuePos = m_nativeHandle.queue_position();
1527
1528 m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
1529
1530 lt::add_torrent_params p = m_ltAddTorrentParams;
1531 p.flags |= lt::torrent_flags::update_subscribe
1532 | lt::torrent_flags::override_trackers
1533 | lt::torrent_flags::override_web_seeds;
1534
1535 if (m_isStopped)
1536 {
1537 p.flags |= lt::torrent_flags::paused;
1538 p.flags &= ~lt::torrent_flags::auto_managed;
1539 }
1540 else if (m_operatingMode == TorrentOperatingMode::AutoManaged)
1541 {
1542 p.flags |= (lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1543 }
1544 else
1545 {
1546 p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
1547 }
1548
1549 m_nativeHandle = m_nativeSession->add_torrent(p);
1550 m_nativeHandle.queue_position_set(queuePos);
1551
1552 m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
1553 }
1554
pause()1555 void TorrentImpl::pause()
1556 {
1557 if (!m_isStopped)
1558 {
1559 m_isStopped = true;
1560 m_session->handleTorrentNeedSaveResumeData(this);
1561 m_session->handleTorrentPaused(this);
1562 }
1563
1564 if (m_maintenanceJob == MaintenanceJob::None)
1565 {
1566 setAutoManaged(false);
1567 m_nativeHandle.pause();
1568
1569 m_speedMonitor.reset();
1570 }
1571 }
1572
resume(const TorrentOperatingMode mode)1573 void TorrentImpl::resume(const TorrentOperatingMode mode)
1574 {
1575 if (hasError())
1576 {
1577 m_nativeHandle.clear_error();
1578 m_nativeHandle.unset_flags(lt::torrent_flags::upload_mode);
1579 }
1580
1581 m_operatingMode = mode;
1582
1583 if (m_hasMissingFiles)
1584 {
1585 m_hasMissingFiles = false;
1586 m_isStopped = false;
1587 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
1588 reload();
1589 updateStatus();
1590 return;
1591 }
1592
1593 if (m_isStopped)
1594 {
1595 // Torrent may have been temporarily resumed to perform checking files
1596 // so we have to ensure it will not pause after checking is done.
1597 m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
1598
1599 m_isStopped = false;
1600 m_session->handleTorrentNeedSaveResumeData(this);
1601 m_session->handleTorrentResumed(this);
1602 }
1603
1604 if (m_maintenanceJob == MaintenanceJob::None)
1605 {
1606 setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
1607 if (m_operatingMode == TorrentOperatingMode::Forced)
1608 m_nativeHandle.resume();
1609 }
1610 }
1611
moveStorage(const QString & newPath,const MoveStorageMode mode)1612 void TorrentImpl::moveStorage(const QString &newPath, const MoveStorageMode mode)
1613 {
1614 if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
1615 {
1616 m_storageIsMoving = true;
1617 updateStatus();
1618 }
1619 }
1620
renameFile(const int index,const QString & path)1621 void TorrentImpl::renameFile(const int index, const QString &path)
1622 {
1623 const QString oldPath = filePath(index);
1624 m_oldPath[lt::file_index_t {index}].push_back(oldPath);
1625 ++m_renameCount;
1626 m_nativeHandle.rename_file(lt::file_index_t {index}, Utils::Fs::toNativePath(path).toStdString());
1627 }
1628
handleStateUpdate(const lt::torrent_status & nativeStatus)1629 void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
1630 {
1631 updateStatus(nativeStatus);
1632 }
1633
handleMoveStorageJobFinished(const bool hasOutstandingJob)1634 void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
1635 {
1636 m_session->handleTorrentNeedSaveResumeData(this);
1637 m_storageIsMoving = hasOutstandingJob;
1638
1639 updateStatus();
1640 const QString newPath = QString::fromStdString(m_nativeStatus.save_path);
1641 if (!useTempPath() && (newPath != m_savePath))
1642 {
1643 m_savePath = newPath;
1644 m_session->handleTorrentSavePathChanged(this);
1645 }
1646
1647 if (!m_storageIsMoving)
1648 {
1649 if (m_hasMissingFiles)
1650 {
1651 // it can be moved to the proper location
1652 m_hasMissingFiles = false;
1653 m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
1654 m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
1655 reload();
1656 updateStatus();
1657 }
1658
1659 while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1660 m_moveFinishedTriggers.takeFirst()();
1661 }
1662 }
1663
handleTrackerReplyAlert(const lt::tracker_reply_alert * p)1664 void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
1665 {
1666 const QString trackerUrl = p->tracker_url();
1667 m_trackerPeerCounts[trackerUrl][p->local_endpoint] = p->num_peers;
1668
1669 m_session->handleTorrentTrackerReply(this, trackerUrl);
1670 }
1671
handleTrackerWarningAlert(const lt::tracker_warning_alert * p)1672 void TorrentImpl::handleTrackerWarningAlert(const lt::tracker_warning_alert *p)
1673 {
1674 const QString trackerUrl = p->tracker_url();
1675 m_session->handleTorrentTrackerWarning(this, trackerUrl);
1676 }
1677
handleTrackerErrorAlert(const lt::tracker_error_alert * p)1678 void TorrentImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p)
1679 {
1680 const QString trackerUrl = p->tracker_url();
1681
1682 // Starting with libtorrent 1.2.x each tracker has multiple local endpoints from which
1683 // an announce is attempted. Some endpoints might succeed while others might fail.
1684 // Emit the signal only if all endpoints have failed.
1685 const QVector<TrackerEntry> trackerList = trackers();
1686 const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry)
1687 {
1688 return (entry.url == trackerUrl);
1689 });
1690 if ((iter != trackerList.cend()) && (iter->status == TrackerEntry::NotWorking))
1691 m_session->handleTorrentTrackerError(this, trackerUrl);
1692 }
1693
handleTorrentCheckedAlert(const lt::torrent_checked_alert * p)1694 void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
1695 {
1696 Q_UNUSED(p);
1697 qDebug("\"%s\" have just finished checking.", qUtf8Printable(name()));
1698
1699
1700 if (!hasMetadata())
1701 {
1702 // The torrent is checked due to metadata received, but we should not process
1703 // this event until the torrent is reloaded using the received metadata.
1704 return;
1705 }
1706
1707 if (m_nativeHandle.need_save_resume_data())
1708 m_session->handleTorrentNeedSaveResumeData(this);
1709
1710 if (m_fastresumeDataRejected && !m_hasMissingFiles)
1711 m_fastresumeDataRejected = false;
1712
1713 updateStatus();
1714
1715 if (!m_hasMissingFiles)
1716 {
1717 if ((progress() < 1.0) && (wantedSize() > 0))
1718 m_hasSeedStatus = false;
1719 else if (progress() == 1.0)
1720 m_hasSeedStatus = true;
1721
1722 adjustActualSavePath();
1723 manageIncompleteFiles();
1724 }
1725
1726 m_session->handleTorrentChecked(this);
1727 }
1728
handleTorrentFinishedAlert(const lt::torrent_finished_alert * p)1729 void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p)
1730 {
1731 Q_UNUSED(p);
1732 qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name()));
1733 qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no");
1734 m_hasMissingFiles = false;
1735 if (m_hasSeedStatus) return;
1736
1737 updateStatus();
1738 m_hasSeedStatus = true;
1739
1740 adjustActualSavePath();
1741 manageIncompleteFiles();
1742
1743 m_session->handleTorrentNeedSaveResumeData(this);
1744
1745 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
1746 if (isMoveInProgress() || (m_renameCount > 0))
1747 {
1748 if (recheckTorrentsOnCompletion)
1749 m_moveFinishedTriggers.append([this]() { forceRecheck(); });
1750 m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); });
1751 }
1752 else
1753 {
1754 if (recheckTorrentsOnCompletion && m_unchecked)
1755 forceRecheck();
1756 m_session->handleTorrentFinished(this);
1757 }
1758 }
1759
handleTorrentPausedAlert(const lt::torrent_paused_alert * p)1760 void TorrentImpl::handleTorrentPausedAlert(const lt::torrent_paused_alert *p)
1761 {
1762 Q_UNUSED(p);
1763 }
1764
handleTorrentResumedAlert(const lt::torrent_resumed_alert * p)1765 void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
1766 {
1767 Q_UNUSED(p);
1768 }
1769
handleSaveResumeDataAlert(const lt::save_resume_data_alert * p)1770 void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
1771 {
1772 if (m_hasMissingFiles)
1773 {
1774 const auto havePieces = m_ltAddTorrentParams.have_pieces;
1775 const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
1776 const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
1777
1778 // Update recent resume data but preserve existing progress
1779 m_ltAddTorrentParams = p->params;
1780 m_ltAddTorrentParams.have_pieces = havePieces;
1781 m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
1782 m_ltAddTorrentParams.verified_pieces = verifiedPieces;
1783 }
1784 else
1785 {
1786 // Update recent resume data
1787 m_ltAddTorrentParams = p->params;
1788 }
1789
1790 m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch();
1791
1792 // We shouldn't save upload_mode flag to allow torrent operate normally on next run
1793 m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
1794
1795 if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
1796 {
1797 m_ltAddTorrentParams.have_pieces.clear();
1798 m_ltAddTorrentParams.verified_pieces.clear();
1799
1800 TorrentInfo metadata = TorrentInfo {m_nativeHandle.torrent_file()};
1801 metadata.setContentLayout(m_contentLayout);
1802
1803 m_session->findIncompleteFiles(metadata, m_savePath);
1804 }
1805
1806 LoadTorrentParams resumeData;
1807 resumeData.name = m_name;
1808 resumeData.category = m_category;
1809 resumeData.savePath = m_useAutoTMM ? "" : m_savePath;
1810 resumeData.tags = m_tags;
1811 resumeData.contentLayout = m_contentLayout;
1812 resumeData.ratioLimit = m_ratioLimit;
1813 resumeData.seedingTimeLimit = m_seedingTimeLimit;
1814 resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
1815 resumeData.hasSeedStatus = m_hasSeedStatus;
1816 resumeData.paused = m_isStopped;
1817 resumeData.forced = (m_operatingMode == TorrentOperatingMode::Forced);
1818 resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
1819
1820 m_session->handleTorrentResumeDataReady(this, resumeData);
1821 }
1822
handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert * p)1823 void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
1824 {
1825 Q_UNUSED(p);
1826 Q_ASSERT_X(false, Q_FUNC_INFO, "This point should be unreachable since libtorrent 1.2.11");
1827 }
1828
handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert * p)1829 void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
1830 {
1831 m_fastresumeDataRejected = true;
1832
1833 if (p->error.value() == lt::errors::mismatching_file_size)
1834 {
1835 // Mismatching file size (files were probably moved)
1836 m_hasMissingFiles = true;
1837 LogMsg(tr("File sizes mismatch for torrent '%1'. Cannot proceed further.").arg(name()), Log::CRITICAL);
1838 }
1839 else
1840 {
1841 LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
1842 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
1843 }
1844 }
1845
handleFileRenamedAlert(const lt::file_renamed_alert * p)1846 void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
1847 {
1848 // Remove empty leftover folders
1849 // For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
1850 // be removed if they are empty
1851 const QString oldFilePath = m_oldPath[p->index].takeFirst();
1852 const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
1853
1854 if (m_oldPath[p->index].isEmpty())
1855 m_oldPath.remove(p->index);
1856
1857 QVector<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
1858 oldPathParts.removeLast(); // drop file name part
1859 QVector<QStringRef> newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts);
1860 newPathParts.removeLast(); // drop file name part
1861
1862 #if defined(Q_OS_WIN)
1863 const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
1864 #else
1865 const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
1866 #endif
1867
1868 int pathIdx = 0;
1869 while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size()))
1870 {
1871 if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
1872 break;
1873 ++pathIdx;
1874 }
1875
1876 for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i)
1877 {
1878 QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
1879 oldPathParts.removeLast();
1880 }
1881
1882 --m_renameCount;
1883 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1884 m_moveFinishedTriggers.takeFirst()();
1885
1886 m_session->handleTorrentNeedSaveResumeData(this);
1887 }
1888
handleFileRenameFailedAlert(const lt::file_rename_failed_alert * p)1889 void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
1890 {
1891 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
1892 .arg(name(), filePath(static_cast<LTUnderlyingType<lt::file_index_t>>(p->index))
1893 , QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
1894
1895 m_oldPath[p->index].removeFirst();
1896 if (m_oldPath[p->index].isEmpty())
1897 m_oldPath.remove(p->index);
1898
1899 --m_renameCount;
1900 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1901 m_moveFinishedTriggers.takeFirst()();
1902
1903 m_session->handleTorrentNeedSaveResumeData(this);
1904 }
1905
handleFileCompletedAlert(const lt::file_completed_alert * p)1906 void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
1907 {
1908 qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
1909 if (m_session->isAppendExtensionEnabled())
1910 {
1911 QString name = filePath(static_cast<LTUnderlyingType<lt::file_index_t>>(p->index));
1912 if (name.endsWith(QB_EXT))
1913 {
1914 const QString oldName = name;
1915 name.chop(QB_EXT.size());
1916 qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name));
1917 renameFile(static_cast<LTUnderlyingType<lt::file_index_t>>(p->index), name);
1918 }
1919 }
1920 }
1921
handleFileErrorAlert(const lt::file_error_alert * p)1922 void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
1923 {
1924 m_lastFileError = {p->error, p->op};
1925 }
1926
handleMetadataReceivedAlert(const lt::metadata_received_alert * p)1927 void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
1928 {
1929 Q_UNUSED(p);
1930 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
1931
1932 m_maintenanceJob = MaintenanceJob::HandleMetadata;
1933 m_session->handleTorrentNeedSaveResumeData(this);
1934 }
1935
handlePerformanceAlert(const lt::performance_alert * p) const1936 void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
1937 {
1938 LogMsg((tr("Performance alert: ") + QString::fromStdString(p->message()))
1939 , Log::INFO);
1940 }
1941
handleTempPathChanged()1942 void TorrentImpl::handleTempPathChanged()
1943 {
1944 adjustActualSavePath();
1945 }
1946
handleCategorySavePathChanged()1947 void TorrentImpl::handleCategorySavePathChanged()
1948 {
1949 if (m_useAutoTMM)
1950 move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
1951 }
1952
handleAppendExtensionToggled()1953 void TorrentImpl::handleAppendExtensionToggled()
1954 {
1955 if (!hasMetadata()) return;
1956
1957 manageIncompleteFiles();
1958 }
1959
handleAlert(const lt::alert * a)1960 void TorrentImpl::handleAlert(const lt::alert *a)
1961 {
1962 switch (a->type())
1963 {
1964 case lt::file_renamed_alert::alert_type:
1965 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
1966 break;
1967 case lt::file_rename_failed_alert::alert_type:
1968 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
1969 break;
1970 case lt::file_completed_alert::alert_type:
1971 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
1972 break;
1973 case lt::file_error_alert::alert_type:
1974 handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
1975 break;
1976 case lt::torrent_finished_alert::alert_type:
1977 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
1978 break;
1979 case lt::save_resume_data_alert::alert_type:
1980 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
1981 break;
1982 case lt::save_resume_data_failed_alert::alert_type:
1983 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
1984 break;
1985 case lt::torrent_paused_alert::alert_type:
1986 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
1987 break;
1988 case lt::torrent_resumed_alert::alert_type:
1989 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
1990 break;
1991 case lt::tracker_error_alert::alert_type:
1992 handleTrackerErrorAlert(static_cast<const lt::tracker_error_alert*>(a));
1993 break;
1994 case lt::tracker_reply_alert::alert_type:
1995 handleTrackerReplyAlert(static_cast<const lt::tracker_reply_alert*>(a));
1996 break;
1997 case lt::tracker_warning_alert::alert_type:
1998 handleTrackerWarningAlert(static_cast<const lt::tracker_warning_alert*>(a));
1999 break;
2000 case lt::metadata_received_alert::alert_type:
2001 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2002 break;
2003 case lt::fastresume_rejected_alert::alert_type:
2004 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2005 break;
2006 case lt::torrent_checked_alert::alert_type:
2007 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2008 break;
2009 case lt::performance_alert::alert_type:
2010 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2011 break;
2012 }
2013 }
2014
manageIncompleteFiles()2015 void TorrentImpl::manageIncompleteFiles()
2016 {
2017 const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
2018 const QVector<qreal> fp = filesProgress();
2019 if (fp.size() != filesCount())
2020 {
2021 qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
2022 return;
2023 }
2024
2025 for (int i = 0; i < filesCount(); ++i)
2026 {
2027 QString name = filePath(i);
2028 if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1))
2029 {
2030 if (!name.endsWith(QB_EXT, Qt::CaseInsensitive))
2031 {
2032 const QString newName = name + QB_EXT;
2033 qDebug() << "Renaming" << name << "to" << newName;
2034 renameFile(i, newName);
2035 }
2036 }
2037 else
2038 {
2039 if (name.endsWith(QB_EXT, Qt::CaseInsensitive))
2040 {
2041 const QString oldName = name;
2042 name.chop(QB_EXT.size());
2043 qDebug() << "Renaming" << oldName << "to" << name;
2044 renameFile(i, name);
2045 }
2046 }
2047 }
2048 }
2049
adjustActualSavePath()2050 void TorrentImpl::adjustActualSavePath()
2051 {
2052 if (!isMoveInProgress())
2053 adjustActualSavePath_impl();
2054 else
2055 m_moveFinishedTriggers.append([this]() { adjustActualSavePath_impl(); });
2056 }
2057
adjustActualSavePath_impl()2058 void TorrentImpl::adjustActualSavePath_impl()
2059 {
2060 const bool needUseTempDir = useTempPath();
2061 const QDir tempDir {m_session->torrentTempPath(info())};
2062 const QDir currentDir {actualStorageLocation()};
2063 const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}};
2064
2065 if (targetDir == currentDir) return;
2066
2067 if (!needUseTempDir)
2068 {
2069 if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()}))
2070 {
2071 // torrent without root folder still has it in its temporary save path
2072 // so its temp path isn't equal to temp path root
2073 const QString currentDirPath = currentDir.absolutePath();
2074 m_moveFinishedTriggers.append([currentDirPath]
2075 {
2076 qDebug() << "Removing torrent temp folder:" << currentDirPath;
2077 Utils::Fs::smartRemoveEmptyFolderTree(currentDirPath);
2078 });
2079 }
2080 }
2081
2082 moveStorage(Utils::Fs::toNativePath(targetDir.absolutePath()), MoveStorageMode::Overwrite);
2083 }
2084
nativeHandle() const2085 lt::torrent_handle TorrentImpl::nativeHandle() const
2086 {
2087 return m_nativeHandle;
2088 }
2089
isMoveInProgress() const2090 bool TorrentImpl::isMoveInProgress() const
2091 {
2092 return m_storageIsMoving;
2093 }
2094
useTempPath() const2095 bool TorrentImpl::useTempPath() const
2096 {
2097 return m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus);
2098 }
2099
updateStatus()2100 void TorrentImpl::updateStatus()
2101 {
2102 updateStatus(m_nativeHandle.status());
2103 }
2104
updateStatus(const lt::torrent_status & nativeStatus)2105 void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
2106 {
2107 m_nativeStatus = nativeStatus;
2108 updateState();
2109
2110 m_speedMonitor.addSample({nativeStatus.download_payload_rate
2111 , nativeStatus.upload_payload_rate});
2112
2113 if (hasMetadata())
2114 {
2115 // NOTE: Don't change the order of these conditionals!
2116 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2117 if (isChecking())
2118 m_unchecked = false;
2119 else if (isDownloading())
2120 m_unchecked = true;
2121 }
2122 }
2123
setRatioLimit(qreal limit)2124 void TorrentImpl::setRatioLimit(qreal limit)
2125 {
2126 if (limit < USE_GLOBAL_RATIO)
2127 limit = NO_RATIO_LIMIT;
2128 else if (limit > MAX_RATIO)
2129 limit = MAX_RATIO;
2130
2131 if (m_ratioLimit != limit)
2132 {
2133 m_ratioLimit = limit;
2134 m_session->handleTorrentNeedSaveResumeData(this);
2135 m_session->handleTorrentShareLimitChanged(this);
2136 }
2137 }
2138
setSeedingTimeLimit(int limit)2139 void TorrentImpl::setSeedingTimeLimit(int limit)
2140 {
2141 if (limit < USE_GLOBAL_SEEDING_TIME)
2142 limit = NO_SEEDING_TIME_LIMIT;
2143 else if (limit > MAX_SEEDING_TIME)
2144 limit = MAX_SEEDING_TIME;
2145
2146 if (m_seedingTimeLimit != limit)
2147 {
2148 m_seedingTimeLimit = limit;
2149 m_session->handleTorrentNeedSaveResumeData(this);
2150 m_session->handleTorrentShareLimitChanged(this);
2151 }
2152 }
2153
setUploadLimit(const int limit)2154 void TorrentImpl::setUploadLimit(const int limit)
2155 {
2156 if (limit == uploadLimit())
2157 return;
2158
2159 m_nativeHandle.set_upload_limit(limit);
2160 m_session->handleTorrentNeedSaveResumeData(this);
2161 }
2162
setDownloadLimit(const int limit)2163 void TorrentImpl::setDownloadLimit(const int limit)
2164 {
2165 if (limit == downloadLimit())
2166 return;
2167
2168 m_nativeHandle.set_download_limit(limit);
2169 m_session->handleTorrentNeedSaveResumeData(this);
2170 }
2171
setSuperSeeding(const bool enable)2172 void TorrentImpl::setSuperSeeding(const bool enable)
2173 {
2174 if (enable == superSeeding())
2175 return;
2176
2177 if (enable)
2178 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2179 else
2180 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2181
2182 m_session->handleTorrentNeedSaveResumeData(this);
2183 }
2184
setDHTDisabled(const bool disable)2185 void TorrentImpl::setDHTDisabled(const bool disable)
2186 {
2187 if (disable == isDHTDisabled())
2188 return;
2189
2190 if (disable)
2191 m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
2192 else
2193 m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
2194
2195 m_session->handleTorrentNeedSaveResumeData(this);
2196 }
2197
setPEXDisabled(const bool disable)2198 void TorrentImpl::setPEXDisabled(const bool disable)
2199 {
2200 if (disable == isPEXDisabled())
2201 return;
2202
2203 if (disable)
2204 m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
2205 else
2206 m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
2207
2208 m_session->handleTorrentNeedSaveResumeData(this);
2209 }
2210
setLSDDisabled(const bool disable)2211 void TorrentImpl::setLSDDisabled(const bool disable)
2212 {
2213 if (disable == isLSDDisabled())
2214 return;
2215
2216 if (disable)
2217 m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
2218 else
2219 m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
2220
2221 m_session->handleTorrentNeedSaveResumeData(this);
2222 }
2223
flushCache() const2224 void TorrentImpl::flushCache() const
2225 {
2226 m_nativeHandle.flush_cache();
2227 }
2228
createMagnetURI() const2229 QString TorrentImpl::createMagnetURI() const
2230 {
2231 return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
2232 }
2233
prioritizeFiles(const QVector<DownloadPriority> & priorities)2234 void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2235 {
2236 if (!hasMetadata()) return;
2237 if (priorities.size() != filesCount()) return;
2238
2239 // Reset 'm_hasSeedStatus' if needed in order to react again to
2240 // 'torrent_finished_alert' and eg show tray notifications
2241 const QVector<qreal> progress = filesProgress();
2242 const QVector<DownloadPriority> oldPriorities = filePriorities();
2243 for (int i = 0; i < oldPriorities.size(); ++i)
2244 {
2245 if ((oldPriorities[i] == DownloadPriority::Ignored)
2246 && (priorities[i] > DownloadPriority::Ignored)
2247 && (progress[i] < 1.0))
2248 {
2249 m_hasSeedStatus = false;
2250 break;
2251 }
2252 }
2253
2254 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2255 m_nativeHandle.prioritize_files(toLTDownloadPriorities(priorities));
2256
2257 // Restore first/last piece first option if necessary
2258 if (m_hasFirstLastPiecePriority)
2259 applyFirstLastPiecePriority(true, priorities);
2260 }
2261
availableFileFractions() const2262 QVector<qreal> TorrentImpl::availableFileFractions() const
2263 {
2264 const int filesCount = this->filesCount();
2265 if (filesCount <= 0) return {};
2266
2267 const QVector<int> piecesAvailability = pieceAvailability();
2268 // libtorrent returns empty array for seeding only torrents
2269 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
2270
2271 QVector<qreal> res;
2272 res.reserve(filesCount);
2273 const TorrentInfo info = this->info();
2274 for (int i = 0; i < filesCount; ++i)
2275 {
2276 const TorrentInfo::PieceRange filePieces = info.filePieces(i);
2277
2278 int availablePieces = 0;
2279 for (const int piece : filePieces)
2280 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2281
2282 const qreal availability = filePieces.isEmpty()
2283 ? 1 // the file has no pieces, so it is available by default
2284 : static_cast<qreal>(availablePieces) / filePieces.size();
2285 res.push_back(availability);
2286 }
2287 return res;
2288 }
2289