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 &params)
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 &params)
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