diff --git a/src/app/application.cpp b/src/app/application.cpp index 844b7118d..f3b115fe4 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -61,6 +61,7 @@ #endif // Q_OS_MACOS #endif +#include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/exceptions.h" diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index ab1d97686..5d5522737 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -22,6 +22,7 @@ bittorrent/session.h bittorrent/sessionstatus.h bittorrent/torrentcreatorthread.h bittorrent/torrenthandle.h +bittorrent/torrenthandleimpl.h bittorrent/torrentinfo.h bittorrent/tracker.h bittorrent/trackerentry.h @@ -100,6 +101,7 @@ bittorrent/private/statistics.cpp bittorrent/session.cpp bittorrent/torrentcreatorthread.cpp bittorrent/torrenthandle.cpp +bittorrent/torrenthandleimpl.cpp bittorrent/torrentinfo.cpp bittorrent/tracker.cpp bittorrent/trackerentry.cpp diff --git a/src/base/base.pri b/src/base/base.pri index e33255930..749c3fade 100644 --- a/src/base/base.pri +++ b/src/base/base.pri @@ -21,6 +21,7 @@ HEADERS += \ $$PWD/bittorrent/sessionstatus.h \ $$PWD/bittorrent/torrentcreatorthread.h \ $$PWD/bittorrent/torrenthandle.h \ + $$PWD/bittorrent/torrenthandleimpl.h \ $$PWD/bittorrent/torrentinfo.h \ $$PWD/bittorrent/tracker.h \ $$PWD/bittorrent/trackerentry.h \ @@ -99,6 +100,7 @@ SOURCES += \ $$PWD/bittorrent/session.cpp \ $$PWD/bittorrent/torrentcreatorthread.cpp \ $$PWD/bittorrent/torrenthandle.cpp \ + $$PWD/bittorrent/torrenthandleimpl.cpp \ $$PWD/bittorrent/torrentinfo.cpp \ $$PWD/bittorrent/tracker.cpp \ $$PWD/bittorrent/trackerentry.cpp \ diff --git a/src/base/bittorrent/peerinfo.cpp b/src/base/bittorrent/peerinfo.cpp index e0a50fd3a..21c13a0ff 100644 --- a/src/base/bittorrent/peerinfo.cpp +++ b/src/base/bittorrent/peerinfo.cpp @@ -28,6 +28,8 @@ #include "peerinfo.h" +#include + #include #include "base/bittorrent/torrenthandle.h" diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index 5dc059503..1bf2d6e19 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -101,7 +101,7 @@ #include "private/portforwarderimpl.h" #include "private/resumedatasavingmanager.h" #include "private/statistics.h" -#include "torrenthandle.h" +#include "torrenthandleimpl.h" #include "tracker.h" #include "trackerentry.h" @@ -660,7 +660,7 @@ void Session::setTempPathEnabled(const bool enabled) { if (enabled != isTempPathEnabled()) { m_isTempPathEnabled = enabled; - for (TorrentHandle *const torrent : asConst(m_torrents)) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->handleTempPathChanged(); } } @@ -674,7 +674,7 @@ void Session::setAppendExtensionEnabled(const bool enabled) { if (isAppendExtensionEnabled() != enabled) { // append or remove .!qB extension for incomplete files - for (TorrentHandle *const torrent : asConst(m_torrents)) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->handleAppendExtensionToggled(); m_isAppendExtensionEnabled = enabled; @@ -825,12 +825,12 @@ bool Session::editCategory(const QString &name, const QString &savePath) m_categories[name] = savePath; m_storedCategories = map_cast(m_categories); if (isDisableAutoTMMWhenCategorySavePathChanged()) { - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) if (torrent->category() == name) torrent->setAutoTMMEnabled(false); } else { - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) if (torrent->category() == name) torrent->handleCategorySavePathChanged(); } @@ -840,7 +840,7 @@ bool Session::editCategory(const QString &name, const QString &savePath) bool Session::removeCategory(const QString &name) { - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) if (torrent->belongsToCategory(name)) torrent->setCategory(""); @@ -927,7 +927,7 @@ bool Session::addTag(const QString &tag) bool Session::removeTag(const QString &tag) { if (m_tags.remove(tag)) { - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->removeTag(tag); m_storedTags = m_tags.values(); emit tagRemoved(tag); @@ -1701,7 +1701,7 @@ void Session::processShareLimits() { qDebug("Processing share limits..."); - for (TorrentHandle *const torrent : asConst(torrents())) { + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) { if (torrent->isSeed() && !torrent->isForced()) { if (torrent->ratioLimit() != TorrentHandle::NO_RATIO_LIMIT) { const qreal ratio = torrent->realRatio(); @@ -1793,7 +1793,7 @@ TorrentHandle *Session::findTorrent(const InfoHash &hash) const bool Session::hasActiveTorrents() const { - return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentHandle *torrent) + return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentHandleImpl *torrent) { return TorrentFilter::ActiveTorrent.match(torrent); }); @@ -1801,7 +1801,7 @@ bool Session::hasActiveTorrents() const bool Session::hasUnfinishedTorrents() const { - return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandle *torrent) + return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandleImpl *torrent) { return (!torrent->isSeed() && !torrent->isPaused()); }); @@ -1809,7 +1809,7 @@ bool Session::hasUnfinishedTorrents() const bool Session::hasRunningSeed() const { - return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandle *torrent) + return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandleImpl *torrent) { return (torrent->isSeed() && !torrent->isPaused()); }); @@ -1837,7 +1837,7 @@ void Session::banIP(const QString &ip) // and from the disk, if the corresponding deleteOption is chosen bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOption) { - TorrentHandle *const torrent = m_torrents.take(hash); + TorrentHandleImpl *const torrent = m_torrents.take(hash); if (!torrent) return false; qDebug("Deleting torrent with hash: %s", qUtf8Printable(torrent->hash())); @@ -1922,21 +1922,21 @@ bool Session::cancelLoadMetadata(const InfoHash &hash) void Session::increaseTorrentsQueuePos(const QVector &hashes) { - using ElementType = std::pair; + using ElementType = std::pair; std::priority_queue , std::greater> torrentQueue; // Sort torrents by queue position for (const InfoHash &infoHash : hashes) { - TorrentHandle *const torrent = m_torrents.value(infoHash); + TorrentHandleImpl *const torrent = m_torrents.value(infoHash); if (torrent && !torrent->isSeed()) torrentQueue.emplace(torrent->queuePosition(), torrent); } // Increase torrents queue position (starting with the one in the highest queue position) while (!torrentQueue.empty()) { - const TorrentHandle *torrent = torrentQueue.top().second; + const TorrentHandleImpl *torrent = torrentQueue.top().second; torrentQueuePositionUp(torrent->nativeHandle()); torrentQueue.pop(); } @@ -1946,19 +1946,19 @@ void Session::increaseTorrentsQueuePos(const QVector &hashes) void Session::decreaseTorrentsQueuePos(const QVector &hashes) { - using ElementType = std::pair; + using ElementType = std::pair; std::priority_queue torrentQueue; // Sort torrents by queue position for (const InfoHash &infoHash : hashes) { - TorrentHandle *const torrent = m_torrents.value(infoHash); + TorrentHandleImpl *const torrent = m_torrents.value(infoHash); if (torrent && !torrent->isSeed()) torrentQueue.emplace(torrent->queuePosition(), torrent); } // Decrease torrents queue position (starting with the one in the lowest queue position) while (!torrentQueue.empty()) { - const TorrentHandle *torrent = torrentQueue.top().second; + const TorrentHandleImpl *torrent = torrentQueue.top().second; torrentQueuePositionDown(torrent->nativeHandle()); torrentQueue.pop(); } @@ -1971,19 +1971,19 @@ void Session::decreaseTorrentsQueuePos(const QVector &hashes) void Session::topTorrentsQueuePos(const QVector &hashes) { - using ElementType = std::pair; + using ElementType = std::pair; std::priority_queue torrentQueue; // Sort torrents by queue position for (const InfoHash &infoHash : hashes) { - TorrentHandle *const torrent = m_torrents.value(infoHash); + TorrentHandleImpl *const torrent = m_torrents.value(infoHash); if (torrent && !torrent->isSeed()) torrentQueue.emplace(torrent->queuePosition(), torrent); } // Top torrents queue position (starting with the one in the lowest queue position) while (!torrentQueue.empty()) { - const TorrentHandle *torrent = torrentQueue.top().second; + const TorrentHandleImpl *torrent = torrentQueue.top().second; torrentQueuePositionTop(torrent->nativeHandle()); torrentQueue.pop(); } @@ -1993,21 +1993,21 @@ void Session::topTorrentsQueuePos(const QVector &hashes) void Session::bottomTorrentsQueuePos(const QVector &hashes) { - using ElementType = std::pair; + using ElementType = std::pair; std::priority_queue , std::greater> torrentQueue; // Sort torrents by queue position for (const InfoHash &infoHash : hashes) { - TorrentHandle *const torrent = m_torrents.value(infoHash); + TorrentHandleImpl *const torrent = m_torrents.value(infoHash); if (torrent && !torrent->isSeed()) torrentQueue.emplace(torrent->queuePosition(), torrent); } // Bottom torrents queue position (starting with the one in the highest queue position) while (!torrentQueue.empty()) { - const TorrentHandle *torrent = torrentQueue.top().second; + const TorrentHandleImpl *torrent = torrentQueue.top().second; torrentQueuePositionBottom(torrent->nativeHandle()); torrentQueue.pop(); } @@ -2018,15 +2018,20 @@ void Session::bottomTorrentsQueuePos(const QVector &hashes) saveTorrentsQueue(); } -void Session::handleTorrentSaveResumeDataRequested(const TorrentHandle *torrent) +void Session::handleTorrentSaveResumeDataRequested(const TorrentHandleImpl *torrent) { qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name())); ++m_numResumeData; } -QHash Session::torrents() const +QVector Session::torrents() const { - return m_torrents; + QVector result; + result.reserve(m_torrents.size()); + for (TorrentHandleImpl *torrent : asConst(m_torrents)) + result << torrent; + + return result; } bool Session::addTorrent(const QString &source, const AddTorrentParams ¶ms) @@ -2327,7 +2332,7 @@ bool Session::addTorrent_impl(CreateTorrentParams params, const MagnetUri &magne if (m_addingTorrents.contains(hash) || m_loadedMetadata.contains(hash)) return false; - TorrentHandle *const torrent = m_torrents.value(hash); + TorrentHandleImpl *const torrent = m_torrents.value(hash); if (torrent) { // a duplicate torrent is added if (torrent->isPrivate() || (!fromMagnetUri && torrentInfo.isPrivate())) return false; @@ -2513,7 +2518,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri) return true; } -void Session::exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder) +void Session::exportTorrentFile(const TorrentHandle *torrent, TorrentExportFolder folder) { Q_ASSERT(((folder == TorrentExportFolder::Regular) && !torrentExportDirectory().isEmpty()) || ((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty())); @@ -2539,7 +2544,7 @@ void Session::exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolde void Session::generateResumeData(const bool final) { - for (TorrentHandle *const torrent : asConst(m_torrents)) { + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) { if (!torrent->isValid()) continue; if (!final && !torrent->needSaveResumeData()) continue; @@ -2586,7 +2591,7 @@ void Session::saveTorrentsQueue() { // store hash in textual representation QMap queue; // Use QMap since it should be ordered by key - for (const TorrentHandle *torrent : asConst(torrents())) { + for (const TorrentHandleImpl *torrent : asConst(m_torrents)) { // We require actual (non-cached) queue position here! const int queuePos = LTUnderlyingType {torrent->nativeHandle().queue_position()}; if (queuePos >= 0) @@ -2627,10 +2632,10 @@ void Session::setDefaultSavePath(QString path) m_defaultSavePath = path; if (isDisableAutoTMMWhenDefaultSavePathChanged()) - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->setAutoTMMEnabled(false); else - for (TorrentHandle *const torrent : asConst(torrents())) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->handleCategorySavePathChanged(); } @@ -2641,7 +2646,7 @@ void Session::setTempPath(QString path) m_tempPath = path; - for (TorrentHandle *const torrent : asConst(m_torrents)) + for (TorrentHandleImpl *const torrent : asConst(m_torrents)) torrent->handleTempPathChanged(); } @@ -3816,48 +3821,48 @@ void Session::updateSeedingLimitTimer() } } -void Session::handleTorrentShareLimitChanged(TorrentHandle *const torrent) +void Session::handleTorrentShareLimitChanged(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); updateSeedingLimitTimer(); } -void Session::handleTorrentNameChanged(TorrentHandle *const torrent) +void Session::handleTorrentNameChanged(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); } -void Session::handleTorrentSavePathChanged(TorrentHandle *const torrent) +void Session::handleTorrentSavePathChanged(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); emit torrentSavePathChanged(torrent); } -void Session::handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory) +void Session::handleTorrentCategoryChanged(TorrentHandleImpl *const torrent, const QString &oldCategory) { torrent->saveResumeData(); emit torrentCategoryChanged(torrent, oldCategory); } -void Session::handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag) +void Session::handleTorrentTagAdded(TorrentHandleImpl *const torrent, const QString &tag) { torrent->saveResumeData(); emit torrentTagAdded(torrent, tag); } -void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag) +void Session::handleTorrentTagRemoved(TorrentHandleImpl *const torrent, const QString &tag) { torrent->saveResumeData(); emit torrentTagRemoved(torrent, tag); } -void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent) +void Session::handleTorrentSavingModeChanged(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); emit torrentSavingModeChanged(torrent); } -void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QVector &newTrackers) +void Session::handleTorrentTrackersAdded(TorrentHandleImpl *const torrent, const QVector &newTrackers) { torrent->saveResumeData(); @@ -3869,7 +3874,7 @@ void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QVe emit trackersChanged(torrent); } -void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const QVector &deletedTrackers) +void Session::handleTorrentTrackersRemoved(TorrentHandleImpl *const torrent, const QVector &deletedTrackers) { torrent->saveResumeData(); @@ -3881,27 +3886,27 @@ void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const Q emit trackersChanged(torrent); } -void Session::handleTorrentTrackersChanged(TorrentHandle *const torrent) +void Session::handleTorrentTrackersChanged(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); emit trackersChanged(torrent); } -void Session::handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QVector &newUrlSeeds) +void Session::handleTorrentUrlSeedsAdded(TorrentHandleImpl *const torrent, const QVector &newUrlSeeds) { torrent->saveResumeData(); for (const QUrl &newUrlSeed : newUrlSeeds) LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name())); } -void Session::handleTorrentUrlSeedsRemoved(TorrentHandle *const torrent, const QVector &urlSeeds) +void Session::handleTorrentUrlSeedsRemoved(TorrentHandleImpl *const torrent, const QVector &urlSeeds) { torrent->saveResumeData(); for (const QUrl &urlSeed : urlSeeds) LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name())); } -void Session::handleTorrentMetadataReceived(TorrentHandle *const torrent) +void Session::handleTorrentMetadataReceived(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); @@ -3922,25 +3927,25 @@ void Session::handleTorrentMetadataReceived(TorrentHandle *const torrent) emit torrentMetadataLoaded(torrent); } -void Session::handleTorrentPaused(TorrentHandle *const torrent) +void Session::handleTorrentPaused(TorrentHandleImpl *const torrent) { if (!torrent->hasError() && !torrent->hasMissingFiles()) torrent->saveResumeData(); emit torrentPaused(torrent); } -void Session::handleTorrentResumed(TorrentHandle *const torrent) +void Session::handleTorrentResumed(TorrentHandleImpl *const torrent) { torrent->saveResumeData(); emit torrentResumed(torrent); } -void Session::handleTorrentChecked(TorrentHandle *const torrent) +void Session::handleTorrentChecked(TorrentHandleImpl *const torrent) { emit torrentFinishedChecking(torrent); } -void Session::handleTorrentFinished(TorrentHandle *const torrent) +void Session::handleTorrentFinished(TorrentHandleImpl *const torrent) { if (!torrent->hasError() && !torrent->hasMissingFiles()) torrent->saveResumeData(); @@ -3975,7 +3980,7 @@ void Session::handleTorrentFinished(TorrentHandle *const torrent) emit allTorrentsFinished(); } -void Session::handleTorrentResumeDataReady(TorrentHandle *const torrent, const std::shared_ptr &data) +void Session::handleTorrentResumeDataReady(TorrentHandleImpl *const torrent, const std::shared_ptr &data) { --m_numResumeData; @@ -3992,23 +3997,23 @@ void Session::handleTorrentResumeDataReady(TorrentHandle *const torrent, const s #endif } -void Session::handleTorrentResumeDataFailed(TorrentHandle *const torrent) +void Session::handleTorrentResumeDataFailed(TorrentHandleImpl *const torrent) { Q_UNUSED(torrent) --m_numResumeData; } -void Session::handleTorrentTrackerReply(TorrentHandle *const torrent, const QString &trackerUrl) +void Session::handleTorrentTrackerReply(TorrentHandleImpl *const torrent, const QString &trackerUrl) { emit trackerSuccess(torrent, trackerUrl); } -void Session::handleTorrentTrackerError(TorrentHandle *const torrent, const QString &trackerUrl) +void Session::handleTorrentTrackerError(TorrentHandleImpl *const torrent, const QString &trackerUrl) { emit trackerError(torrent, trackerUrl); } -bool Session::addMoveTorrentStorageJob(TorrentHandle *torrent, const QString &newPath, const MoveStorageMode mode) +bool Session::addMoveTorrentStorageJob(TorrentHandleImpl *torrent, const QString &newPath, const MoveStorageMode mode) { Q_ASSERT(torrent); @@ -4081,14 +4086,14 @@ void Session::handleMoveTorrentStorageJobFinished(const QString &errorMessage) } } -void Session::handleTorrentTrackerWarning(TorrentHandle *const torrent, const QString &trackerUrl) +void Session::handleTorrentTrackerWarning(TorrentHandleImpl *const torrent, const QString &trackerUrl) { emit trackerWarning(torrent, trackerUrl); } bool Session::hasPerTorrentRatioLimit() const { - return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentHandle *torrent) + return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentHandleImpl *torrent) { return (torrent->ratioLimit() >= 0); }); @@ -4096,7 +4101,7 @@ bool Session::hasPerTorrentRatioLimit() const bool Session::hasPerTorrentSeedingTimeLimit() const { - return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentHandle *torrent) + return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentHandleImpl *torrent) { return (torrent->seedingTimeLimit() >= 0); }); @@ -4170,7 +4175,7 @@ void Session::disableIPFilter() void Session::recursiveTorrentDownload(const InfoHash &hash) { - TorrentHandle *const torrent = m_torrents.value(hash); + TorrentHandleImpl *const torrent = m_torrents.value(hash); if (!torrent) return; for (int i = 0; i < torrent->filesCount(); ++i) { @@ -4480,7 +4485,7 @@ void Session::handleAlert(const lt::alert *a) void Session::dispatchTorrentAlert(const lt::alert *a) { - TorrentHandle *const torrent = m_torrents.value(static_cast(a)->handle.info_hash()); + TorrentHandleImpl *const torrent = m_torrents.value(static_cast(a)->handle.info_hash()); if (torrent) { torrent->handleAlert(a); return; @@ -4500,7 +4505,7 @@ void Session::createTorrentHandle(const lt::torrent_handle &nativeHandle) const CreateTorrentParams params = m_addingTorrents.take(nativeHandle.info_hash()); - TorrentHandle *const torrent = new TorrentHandle(this, nativeHandle, params); + TorrentHandleImpl *const torrent = new TorrentHandleImpl(this, nativeHandle, params); m_torrents.insert(torrent->hash(), torrent); const bool fromMagnetUri = !torrent->hasMetadata(); @@ -4644,7 +4649,7 @@ void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) void Session::handleFileErrorAlert(const lt::file_error_alert *p) { - TorrentHandle *const torrent = m_torrents.value(p->handle.info_hash()); + TorrentHandleImpl *const torrent = m_torrents.value(p->handle.info_hash()); if (!torrent) return; @@ -4738,7 +4743,7 @@ void Session::handlePeerBanAlert(const lt::peer_ban_alert *p) void Session::handleUrlSeedAlert(const lt::url_seed_alert *p) { - const TorrentHandle *torrent = m_torrents.value(p->handle.info_hash()); + const TorrentHandleImpl *torrent = m_torrents.value(p->handle.info_hash()); if (!torrent) return; @@ -4969,7 +4974,7 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p) { if (m_moveStorageQueue.isEmpty()) return; - const TorrentHandle *torrent = m_torrents.value(p->handle.info_hash()); + const TorrentHandleImpl *torrent = m_torrents.value(p->handle.info_hash()); const MoveStorageJob ¤tJob = m_moveStorageQueue.first(); if (currentJob.torrent != torrent) return; @@ -4981,7 +4986,7 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert { if (m_moveStorageQueue.isEmpty()) return; - const TorrentHandle *torrent = m_torrents.value(p->handle.info_hash()); + const TorrentHandleImpl *torrent = m_torrents.value(p->handle.info_hash()); const MoveStorageJob ¤tJob = m_moveStorageQueue.first(); if (currentJob.torrent != torrent) return; @@ -4994,7 +4999,7 @@ void Session::handleStateUpdateAlert(const lt::state_update_alert *p) updatedTorrents.reserve(p->status.size()); for (const lt::torrent_status &status : p->status) { - TorrentHandle *const torrent = m_torrents.value(status.info_hash); + TorrentHandleImpl *const torrent = m_torrents.value(status.info_hash); if (!torrent) continue; diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index e0c416742..f760233c8 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -93,6 +93,7 @@ namespace BitTorrent class InfoHash; class MagnetUri; class TorrentHandle; + class TorrentHandleImpl; class Tracker; class TrackerEntry; struct CreateTorrentParams; @@ -411,7 +412,7 @@ namespace BitTorrent void startUpTorrents(); TorrentHandle *findTorrent(const InfoHash &hash) const; - QHash torrents() const; + QVector torrents() const; bool hasActiveTorrents() const; bool hasUnfinishedTorrents() const; bool hasRunningSeed() const; @@ -440,31 +441,31 @@ namespace BitTorrent void bottomTorrentsQueuePos(const QVector &hashes); // TorrentHandle interface - void handleTorrentSaveResumeDataRequested(const TorrentHandle *torrent); - void handleTorrentShareLimitChanged(TorrentHandle *const torrent); - void handleTorrentNameChanged(TorrentHandle *const torrent); - void handleTorrentSavePathChanged(TorrentHandle *const torrent); - void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory); - void handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag); - void handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag); - void handleTorrentSavingModeChanged(TorrentHandle *const torrent); - void handleTorrentMetadataReceived(TorrentHandle *const torrent); - void handleTorrentPaused(TorrentHandle *const torrent); - void handleTorrentResumed(TorrentHandle *const torrent); - void handleTorrentChecked(TorrentHandle *const torrent); - void handleTorrentFinished(TorrentHandle *const torrent); - void handleTorrentTrackersAdded(TorrentHandle *const torrent, const QVector &newTrackers); - void handleTorrentTrackersRemoved(TorrentHandle *const torrent, const QVector &deletedTrackers); - void handleTorrentTrackersChanged(TorrentHandle *const torrent); - void handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QVector &newUrlSeeds); - void handleTorrentUrlSeedsRemoved(TorrentHandle *const torrent, const QVector &urlSeeds); - void handleTorrentResumeDataReady(TorrentHandle *const torrent, const std::shared_ptr &data); - void handleTorrentResumeDataFailed(TorrentHandle *const torrent); - void handleTorrentTrackerReply(TorrentHandle *const torrent, const QString &trackerUrl); - void handleTorrentTrackerWarning(TorrentHandle *const torrent, const QString &trackerUrl); - void handleTorrentTrackerError(TorrentHandle *const torrent, const QString &trackerUrl); - - bool addMoveTorrentStorageJob(TorrentHandle *torrent, const QString &newPath, MoveStorageMode mode); + void handleTorrentSaveResumeDataRequested(const TorrentHandleImpl *torrent); + void handleTorrentShareLimitChanged(TorrentHandleImpl *const torrent); + void handleTorrentNameChanged(TorrentHandleImpl *const torrent); + void handleTorrentSavePathChanged(TorrentHandleImpl *const torrent); + void handleTorrentCategoryChanged(TorrentHandleImpl *const torrent, const QString &oldCategory); + void handleTorrentTagAdded(TorrentHandleImpl *const torrent, const QString &tag); + void handleTorrentTagRemoved(TorrentHandleImpl *const torrent, const QString &tag); + void handleTorrentSavingModeChanged(TorrentHandleImpl *const torrent); + void handleTorrentMetadataReceived(TorrentHandleImpl *const torrent); + void handleTorrentPaused(TorrentHandleImpl *const torrent); + void handleTorrentResumed(TorrentHandleImpl *const torrent); + void handleTorrentChecked(TorrentHandleImpl *const torrent); + void handleTorrentFinished(TorrentHandleImpl *const torrent); + void handleTorrentTrackersAdded(TorrentHandleImpl *const torrent, const QVector &newTrackers); + void handleTorrentTrackersRemoved(TorrentHandleImpl *const torrent, const QVector &deletedTrackers); + void handleTorrentTrackersChanged(TorrentHandleImpl *const torrent); + void handleTorrentUrlSeedsAdded(TorrentHandleImpl *const torrent, const QVector &newUrlSeeds); + void handleTorrentUrlSeedsRemoved(TorrentHandleImpl *const torrent, const QVector &urlSeeds); + void handleTorrentResumeDataReady(TorrentHandleImpl *const torrent, const std::shared_ptr &data); + void handleTorrentResumeDataFailed(TorrentHandleImpl *const torrent); + void handleTorrentTrackerReply(TorrentHandleImpl *const torrent, const QString &trackerUrl); + void handleTorrentTrackerWarning(TorrentHandleImpl *const torrent, const QString &trackerUrl); + void handleTorrentTrackerError(TorrentHandleImpl *const torrent, const QString &trackerUrl); + + bool addMoveTorrentStorageJob(TorrentHandleImpl *torrent, const QString &newPath, MoveStorageMode mode); signals: void addTorrentFailed(const QString &error); @@ -521,7 +522,7 @@ namespace BitTorrent private: struct MoveStorageJob { - TorrentHandle *torrent; + TorrentHandleImpl *torrent; QString path; MoveStorageMode mode; }; @@ -571,7 +572,7 @@ namespace BitTorrent bool findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) const; void updateSeedingLimitTimer(); - void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular); + void exportTorrentFile(const TorrentHandle *torrent, TorrentExportFolder folder = TorrentExportFolder::Regular); void handleAlert(const lt::alert *a); void dispatchTorrentAlert(const lt::alert *a); @@ -733,7 +734,7 @@ namespace BitTorrent ResumeDataSavingManager *m_resumeDataSavingManager = nullptr; QHash m_loadedMetadata; - QHash m_torrents; + QHash m_torrents; QHash m_addingTorrents; QHash m_downloadedTorrents; QHash m_removingTorrents; diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 5f37ecc4c..cd4e11d80 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -29,2163 +29,35 @@ #include "torrenthandle.h" -#include -#include #include -#ifdef Q_OS_WIN -#include -#endif - -#include -#include -#include -#include -#include -#include - -#if (LIBTORRENT_VERSION_NUM >= 10200) -#include -#include -#else -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include "base/global.h" -#include "base/logger.h" -#include "base/preferences.h" -#include "base/profile.h" -#include "base/tristatebool.h" -#include "base/utils/fs.h" -#include "base/utils/string.h" -#include "downloadpriority.h" -#include "peeraddress.h" -#include "peerinfo.h" -#include "private/ltunderlyingtype.h" -#include "session.h" -#include "trackerentry.h" - const QString QB_EXT {QStringLiteral(".!qB")}; -using namespace BitTorrent; - -#if (LIBTORRENT_VERSION_NUM >= 10200) -namespace libtorrent +namespace BitTorrent { - namespace aux + uint qHash(const BitTorrent::TorrentState key, const uint seed) { - template - uint qHash(const strong_typedef &key, const uint seed) - { - return ::qHash((std::hash> {})(key), seed); - } + return ::qHash(static_cast>(key), seed); } -} -#endif -namespace -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - using LTDownloadPriority = int; - using LTPieceIndex = int; - using LTQueuePosition = int; -#else - using LTDownloadPriority = lt::download_priority_t; - using LTPieceIndex = lt::piece_index_t; - using LTQueuePosition = lt::queue_position_t; -#endif + // TorrentHandle - std::vector toLTDownloadPriorities(const QVector &priorities) - { - std::vector out; - out.reserve(priorities.size()); + const qreal TorrentHandle::USE_GLOBAL_RATIO = -2.; + const qreal TorrentHandle::NO_RATIO_LIMIT = -1.; - std::transform(priorities.cbegin(), priorities.cend() - , std::back_inserter(out), [](BitTorrent::DownloadPriority priority) - { - return static_cast( - static_cast>(priority)); - }); - return out; - } + const int TorrentHandle::USE_GLOBAL_SEEDING_TIME = -2; + const int TorrentHandle::NO_SEEDING_TIME_LIMIT = -1; - using ListType = lt::entry::list_type; + const qreal TorrentHandle::MAX_RATIO = 9999.; + const int TorrentHandle::MAX_SEEDING_TIME = 525600; - ListType setToEntryList(const QSet &input) + void TorrentHandle::toggleSequentialDownload() { - ListType entryList; - for (const QString &setValue : input) - entryList.emplace_back(setValue.toStdString()); - return entryList; - } -} - -// AddTorrentData - -CreateTorrentParams::CreateTorrentParams() - : restored(false) - , disableTempPath(false) - , sequential(false) - , firstLastPiecePriority(false) - , hasSeedStatus(false) - , skipChecking(false) - , hasRootFolder(true) - , forced(false) - , paused(false) - , uploadLimit(-1) - , downloadLimit(-1) - , ratioLimit(TorrentHandle::USE_GLOBAL_RATIO) - , seedingTimeLimit(TorrentHandle::USE_GLOBAL_SEEDING_TIME) -{ -} - -CreateTorrentParams::CreateTorrentParams(const AddTorrentParams ¶ms) - : restored(false) - , name(params.name) - , category(params.category) - , tags(params.tags) - , savePath(params.savePath) - , disableTempPath(params.disableTempPath) - , sequential(params.sequential) - , firstLastPiecePriority(params.firstLastPiecePriority) - , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping - , skipChecking(params.skipChecking) - , hasRootFolder(params.createSubfolder == TriStateBool::Undefined - ? Session::instance()->isCreateTorrentSubfolder() - : params.createSubfolder == TriStateBool::True) - , forced(params.addForced == TriStateBool::True) - , paused(params.addPaused == TriStateBool::Undefined - ? Session::instance()->isAddTorrentPaused() - : params.addPaused == TriStateBool::True) - , uploadLimit(params.uploadLimit) - , downloadLimit(params.downloadLimit) - , filePriorities(params.filePriorities) - , ratioLimit(params.ignoreShareLimits ? TorrentHandle::NO_RATIO_LIMIT : TorrentHandle::USE_GLOBAL_RATIO) - , seedingTimeLimit(params.ignoreShareLimits ? TorrentHandle::NO_SEEDING_TIME_LIMIT : TorrentHandle::USE_GLOBAL_SEEDING_TIME) -{ - bool useAutoTMM = (params.useAutoTMM == TriStateBool::Undefined - ? !Session::instance()->isAutoTMMDisabledByDefault() - : params.useAutoTMM == TriStateBool::True); - if (useAutoTMM) - savePath = ""; - else if (savePath.trimmed().isEmpty()) - savePath = Session::instance()->defaultSavePath(); -} - -uint BitTorrent::qHash(const BitTorrent::TorrentState key, const uint seed) -{ - return ::qHash(static_cast>(key), seed); -} - -// TorrentHandle - -const qreal TorrentHandle::USE_GLOBAL_RATIO = -2.; -const qreal TorrentHandle::NO_RATIO_LIMIT = -1.; - -const int TorrentHandle::USE_GLOBAL_SEEDING_TIME = -2; -const int TorrentHandle::NO_SEEDING_TIME_LIMIT = -1; - -const qreal TorrentHandle::MAX_RATIO = 9999.; -const int TorrentHandle::MAX_SEEDING_TIME = 525600; - -TorrentHandle::TorrentHandle(Session *session, const lt::torrent_handle &nativeHandle, - const CreateTorrentParams ¶ms) - : QObject(session) - , m_session(session) - , m_nativeHandle(nativeHandle) - , m_state(TorrentState::Unknown) - , m_renameCount(0) - , m_useAutoTMM(params.savePath.isEmpty()) - , m_name(params.name) - , m_savePath(Utils::Fs::toNativePath(params.savePath)) - , m_category(params.category) - , m_tags(params.tags) - , m_hasSeedStatus(params.hasSeedStatus) - , m_ratioLimit(params.ratioLimit) - , m_seedingTimeLimit(params.seedingTimeLimit) - , m_tempPathDisabled(params.disableTempPath) - , m_fastresumeDataRejected(false) - , m_hasMissingFiles(false) - , m_hasRootFolder(params.hasRootFolder) - , m_needsToSetFirstLastPiecePriority(false) -{ - if (m_useAutoTMM) - m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category)); - - updateStatus(); - m_hash = InfoHash(m_nativeStatus.info_hash); - - // NB: the following two if statements are present because we don't want - // to set either sequential download or first/last piece priority to false - // if their respective flags in data are false when a torrent is being - // resumed. This is because, in that circumstance, this constructor is - // called with those flags set to false, even if the torrent was set to - // download sequentially or have first/last piece priority enabled when - // its resume data was saved. These two settings are restored later. But - // if we set them to false now, both will erroneously not be restored. - if (!params.restored || params.sequential) - setSequentialDownload(params.sequential); - if (!params.restored || params.firstLastPiecePriority) - setFirstLastPiecePriority(params.firstLastPiecePriority); - - if (!params.restored && hasMetadata()) { - if (filesCount() == 1) - m_hasRootFolder = false; - } -} - -TorrentHandle::~TorrentHandle() {} - -bool TorrentHandle::isValid() const -{ - return m_nativeHandle.is_valid(); -} - -InfoHash TorrentHandle::hash() const -{ - return m_hash; -} - -QString TorrentHandle::name() const -{ - QString name = m_name; - if (!name.isEmpty()) return name; - - name = QString::fromStdString(m_nativeStatus.name); - if (!name.isEmpty()) return name; - - if (hasMetadata()) { - name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name()); - if (!name.isEmpty()) return name; - } - - return m_hash; -} - -QDateTime TorrentHandle::creationDate() const -{ - return m_torrentInfo.creationDate(); -} - -QString TorrentHandle::creator() const -{ - return m_torrentInfo.creator(); -} - -QString TorrentHandle::comment() const -{ - return m_torrentInfo.comment(); -} - -bool TorrentHandle::isPrivate() const -{ - return m_torrentInfo.isPrivate(); -} - -qlonglong TorrentHandle::totalSize() const -{ - return m_torrentInfo.totalSize(); -} - -// size without the "don't download" files -qlonglong TorrentHandle::wantedSize() const -{ - return m_nativeStatus.total_wanted; -} - -qlonglong TorrentHandle::completedSize() const -{ - return m_nativeStatus.total_wanted_done; -} - -qlonglong TorrentHandle::incompletedSize() const -{ - return (m_nativeStatus.total_wanted - m_nativeStatus.total_wanted_done); -} - -qlonglong TorrentHandle::pieceLength() const -{ - return m_torrentInfo.pieceLength(); -} - -qlonglong TorrentHandle::wastedSize() const -{ - return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes); -} - -QString TorrentHandle::currentTracker() const -{ - return QString::fromStdString(m_nativeStatus.current_tracker); -} - -QString TorrentHandle::savePath(bool actual) const -{ - if (actual) - return Utils::Fs::toUniformPath(actualStorageLocation()); - else - return Utils::Fs::toUniformPath(m_savePath); -} - -QString TorrentHandle::rootPath(bool actual) const -{ - if ((filesCount() > 1) && !hasRootFolder()) - return {}; - - const QString firstFilePath = filePath(0); - const int slashIndex = firstFilePath.indexOf('/'); - if (slashIndex >= 0) - return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex)); - else - return QDir(savePath(actual)).absoluteFilePath(firstFilePath); -} - -QString TorrentHandle::contentPath(const bool actual) const -{ - if (filesCount() == 1) - return QDir(savePath(actual)).absoluteFilePath(filePath(0)); - - if (hasRootFolder()) - return rootPath(actual); - - return savePath(actual); -} - -bool TorrentHandle::isAutoTMMEnabled() const -{ - return m_useAutoTMM; -} - -void TorrentHandle::setAutoTMMEnabled(bool enabled) -{ - if (m_useAutoTMM == enabled) return; - - m_useAutoTMM = enabled; - m_session->handleTorrentSavingModeChanged(this); - - if (m_useAutoTMM) - move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); -} - -bool TorrentHandle::hasRootFolder() const -{ - return m_hasRootFolder; -} - -QString TorrentHandle::actualStorageLocation() const -{ - return QString::fromStdString(m_nativeStatus.save_path); -} - -bool TorrentHandle::isAutoManaged() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.auto_managed; -#else - return bool {m_nativeStatus.flags & lt::torrent_flags::auto_managed}; -#endif -} - -void TorrentHandle::setAutoManaged(const bool enable) -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - m_nativeHandle.auto_managed(enable); -#else - if (enable) - m_nativeHandle.set_flags(lt::torrent_flags::auto_managed); - else - m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); -#endif -} - -QVector TorrentHandle::trackers() const -{ - const std::vector nativeTrackers = m_nativeHandle.trackers(); - - QVector entries; - entries.reserve(nativeTrackers.size()); - - for (const lt::announce_entry &tracker : nativeTrackers) - entries << tracker; - - return entries; -} - -QHash TorrentHandle::trackerInfos() const -{ - return m_trackerInfos; -} - -void TorrentHandle::addTrackers(const QVector &trackers) -{ - QSet currentTrackers; - for (const lt::announce_entry &entry : m_nativeHandle.trackers()) - currentTrackers << entry; - - QVector newTrackers; - newTrackers.reserve(trackers.size()); - - for (const TrackerEntry &tracker : trackers) { - if (!currentTrackers.contains(tracker)) { - m_nativeHandle.add_tracker(tracker.nativeEntry()); - newTrackers << tracker; - } - } - - if (!newTrackers.isEmpty()) - m_session->handleTorrentTrackersAdded(this, newTrackers); -} - -void TorrentHandle::replaceTrackers(const QVector &trackers) -{ - QVector currentTrackers = this->trackers(); - - QVector newTrackers; - newTrackers.reserve(trackers.size()); - - std::vector nativeTrackers; - nativeTrackers.reserve(trackers.size()); - - for (const TrackerEntry &tracker : trackers) { - nativeTrackers.emplace_back(tracker.nativeEntry()); - - if (!currentTrackers.removeOne(tracker)) - newTrackers << tracker; - } - - m_nativeHandle.replace_trackers(nativeTrackers); - - if (newTrackers.isEmpty() && currentTrackers.isEmpty()) { - // when existing tracker reorders - m_session->handleTorrentTrackersChanged(this); - } - else { - if (!currentTrackers.isEmpty()) - m_session->handleTorrentTrackersRemoved(this, currentTrackers); - - if (!newTrackers.isEmpty()) - m_session->handleTorrentTrackersAdded(this, newTrackers); - } -} - -QVector TorrentHandle::urlSeeds() const -{ - const std::set currentSeeds = m_nativeHandle.url_seeds(); - - QVector urlSeeds; - urlSeeds.reserve(currentSeeds.size()); - - for (const std::string &urlSeed : currentSeeds) - urlSeeds.append(QUrl(urlSeed.c_str())); - - return urlSeeds; -} - -void TorrentHandle::addUrlSeeds(const QVector &urlSeeds) -{ - const std::set currentSeeds = m_nativeHandle.url_seeds(); - - QVector addedUrlSeeds; - addedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) { - const std::string nativeUrl = url.toString().toStdString(); - if (currentSeeds.find(nativeUrl) == currentSeeds.end()) { - m_nativeHandle.add_url_seed(nativeUrl); - addedUrlSeeds << url; - } - } - - if (!addedUrlSeeds.isEmpty()) - m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds); -} - -void TorrentHandle::removeUrlSeeds(const QVector &urlSeeds) -{ - const std::set currentSeeds = m_nativeHandle.url_seeds(); - - QVector removedUrlSeeds; - removedUrlSeeds.reserve(urlSeeds.size()); - - for (const QUrl &url : urlSeeds) { - const std::string nativeUrl = url.toString().toStdString(); - if (currentSeeds.find(nativeUrl) != currentSeeds.end()) { - m_nativeHandle.remove_url_seed(nativeUrl); - removedUrlSeeds << url; - } - } - - if (!removedUrlSeeds.isEmpty()) - m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds); -} - -bool TorrentHandle::connectPeer(const PeerAddress &peerAddress) -{ - lt::error_code ec; -#if (LIBTORRENT_VERSION_NUM < 10200) - const lt::address addr = lt::address::from_string(peerAddress.ip.toString().toStdString(), ec); -#else - const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec); -#endif - if (ec) return false; - - const lt::tcp::endpoint endpoint(addr, peerAddress.port); - try { - m_nativeHandle.connect_peer(endpoint); - } -#if (LIBTORRENT_VERSION_NUM < 10200) - catch (const boost::system::system_error &err) { -#else - catch (const lt::system_error &err) { -#endif - LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3") - .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING); - return false; - } - - LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name())); - return true; -} - -bool TorrentHandle::needSaveResumeData() const -{ - return m_nativeHandle.need_save_resume_data(); -} - -void TorrentHandle::saveResumeData() -{ - m_nativeHandle.save_resume_data(); - m_session->handleTorrentSaveResumeDataRequested(this); -} - -int TorrentHandle::filesCount() const -{ - return m_torrentInfo.filesCount(); -} - -int TorrentHandle::piecesCount() const -{ - return m_torrentInfo.piecesCount(); -} - -int TorrentHandle::piecesHave() const -{ - return m_nativeStatus.num_pieces; -} - -qreal TorrentHandle::progress() const -{ - if (!isChecking()) { - if (!m_nativeStatus.total_wanted) - return 0.; - - if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted) - return 1.; - - const qreal progress = static_cast(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted; - Q_ASSERT((progress >= 0.f) && (progress <= 1.f)); - return progress; - } - - return m_nativeStatus.progress; -} - -QString TorrentHandle::category() const -{ - return m_category; -} - -bool TorrentHandle::belongsToCategory(const QString &category) const -{ - if (m_category.isEmpty()) return category.isEmpty(); - if (!Session::isValidCategoryName(category)) return false; - - if (m_category == category) return true; - - if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/')) - return true; - - return false; -} - -QSet TorrentHandle::tags() const -{ - return m_tags; -} - -bool TorrentHandle::hasTag(const QString &tag) const -{ - return m_tags.contains(tag); -} - -bool TorrentHandle::addTag(const QString &tag) -{ - if (!Session::isValidTag(tag)) - return false; - - if (!hasTag(tag)) { - if (!m_session->hasTag(tag)) - if (!m_session->addTag(tag)) - return false; - m_tags.insert(tag); - m_session->handleTorrentTagAdded(this, tag); - return true; - } - return false; -} - -bool TorrentHandle::removeTag(const QString &tag) -{ - if (m_tags.remove(tag)) { - m_session->handleTorrentTagRemoved(this, tag); - return true; - } - return false; -} - -void TorrentHandle::removeAllTags() -{ - for (const QString &tag : asConst(tags())) - removeTag(tag); -} - -QDateTime TorrentHandle::addedTime() const -{ - return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time); -} - -qreal TorrentHandle::ratioLimit() const -{ - return m_ratioLimit; -} - -int TorrentHandle::seedingTimeLimit() const -{ - return m_seedingTimeLimit; -} - -QString TorrentHandle::filePath(int index) const -{ - return m_torrentInfo.filePath(index); -} - -QString TorrentHandle::fileName(int index) const -{ - if (!hasMetadata()) return {}; - return Utils::Fs::fileName(filePath(index)); -} - -qlonglong TorrentHandle::fileSize(int index) const -{ - return m_torrentInfo.fileSize(index); -} - -// Return a list of absolute paths corresponding -// to all files in a torrent -QStringList TorrentHandle::absoluteFilePaths() const -{ - if (!hasMetadata()) return {}; - - const QDir saveDir(savePath(true)); - QStringList res; - for (int i = 0; i < filesCount(); ++i) - res << Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i))); - return res; -} - -QStringList TorrentHandle::absoluteFilePathsUnwanted() const -{ - if (!hasMetadata()) return {}; - - const QDir saveDir(savePath(true)); -#if (LIBTORRENT_VERSION_NUM < 10200) - const std::vector fp = m_nativeHandle.file_priorities(); -#else - const std::vector fp = m_nativeHandle.get_file_priorities(); -#endif - - QStringList res; - for (int i = 0; i < static_cast(fp.size()); ++i) { - if (fp[i] == LTDownloadPriority {0}) { - const QString path = Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i))); - if (path.contains(".unwanted")) - res << path; - } + setSequentialDownload(!isSequentialDownload()); } - return res; -} - -QVector TorrentHandle::filePriorities() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - const std::vector fp = m_nativeHandle.file_priorities(); -#else - const std::vector fp = m_nativeHandle.get_file_priorities(); -#endif - - QVector ret; - std::transform(fp.cbegin(), fp.cend(), std::back_inserter(ret), [](LTDownloadPriority priority) + void TorrentHandle::toggleFirstLastPiecePriority() { - return static_cast(LTUnderlyingType {priority}); - }); - return ret; -} - -TorrentInfo TorrentHandle::info() const -{ - return m_torrentInfo; -} - -bool TorrentHandle::isPaused() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return (m_nativeStatus.paused && !isAutoManaged()); -#else - return ((m_nativeStatus.flags & lt::torrent_flags::paused) - && !isAutoManaged()); -#endif -} - -bool TorrentHandle::isResumed() const -{ - return !isPaused(); -} - -bool TorrentHandle::isQueued() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return (m_nativeStatus.paused && isAutoManaged()); -#else - return ((m_nativeStatus.flags & lt::torrent_flags::paused) - && isAutoManaged()); -#endif -} - -bool TorrentHandle::isChecking() const -{ - return ((m_nativeStatus.state == lt::torrent_status::checking_files) - || (m_nativeStatus.state == lt::torrent_status::checking_resume_data)); -} - -bool TorrentHandle::isDownloading() const -{ - return m_state == TorrentState::Downloading - || m_state == TorrentState::DownloadingMetadata - || m_state == TorrentState::StalledDownloading - || m_state == TorrentState::CheckingDownloading - || m_state == TorrentState::PausedDownloading - || m_state == TorrentState::QueuedDownloading - || m_state == TorrentState::ForcedDownloading; -} - -bool TorrentHandle::isUploading() const -{ - return m_state == TorrentState::Uploading - || m_state == TorrentState::StalledUploading - || m_state == TorrentState::CheckingUploading - || m_state == TorrentState::QueuedUploading - || m_state == TorrentState::ForcedUploading; -} - -bool TorrentHandle::isCompleted() const -{ - return m_state == TorrentState::Uploading - || m_state == TorrentState::StalledUploading - || m_state == TorrentState::CheckingUploading - || m_state == TorrentState::PausedUploading - || m_state == TorrentState::QueuedUploading - || m_state == TorrentState::ForcedUploading; -} - -bool TorrentHandle::isActive() const -{ - if (m_state == TorrentState::StalledDownloading) - return (uploadPayloadRate() > 0); - - return m_state == TorrentState::DownloadingMetadata - || m_state == TorrentState::Downloading - || m_state == TorrentState::ForcedDownloading - || m_state == TorrentState::Uploading - || m_state == TorrentState::ForcedUploading - || m_state == TorrentState::Moving; -} - -bool TorrentHandle::isInactive() const -{ - return !isActive(); -} - -bool TorrentHandle::isErrored() const -{ - return m_state == TorrentState::MissingFiles - || m_state == TorrentState::Error; -} - -bool TorrentHandle::isSeed() const -{ - // Affected by bug http://code.rasterbar.com/libtorrent/ticket/402 - //bool result; - //result = m_nativeHandle.is_seed()); - //return result; - // May suffer from approximation problems - //return (progress() == 1.); - // This looks safe - return ((m_nativeStatus.state == lt::torrent_status::finished) - || (m_nativeStatus.state == lt::torrent_status::seeding)); -} - -bool TorrentHandle::isForced() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return (!m_nativeStatus.paused && !isAutoManaged()); -#else - return (!(m_nativeStatus.flags & lt::torrent_flags::paused) - && !isAutoManaged()); -#endif -} - -bool TorrentHandle::isSequentialDownload() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.sequential_download; -#else - return bool {m_nativeStatus.flags & lt::torrent_flags::sequential_download}; -#endif -} - -bool TorrentHandle::hasFirstLastPiecePriority() const -{ - if (!hasMetadata()) - return m_needsToSetFirstLastPiecePriority; - -#if (LIBTORRENT_VERSION_NUM < 10200) - const std::vector filePriorities = nativeHandle().file_priorities(); -#else - const std::vector filePriorities = nativeHandle().get_file_priorities(); -#endif - for (int i = 0; i < static_cast(filePriorities.size()); ++i) { - if (filePriorities[i] <= LTDownloadPriority {0}) - continue; - - const TorrentInfo::PieceRange extremities = info().filePieces(i); - const LTDownloadPriority firstPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.first()}); - const LTDownloadPriority lastPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.last()}); - return ((firstPiecePrio == LTDownloadPriority {7}) && (lastPiecePrio == LTDownloadPriority {7})); - } - - return false; -} - -TorrentState TorrentHandle::state() const -{ - return m_state; -} - -void TorrentHandle::updateState() -{ - if (hasError()) { - m_state = TorrentState::Error; - } - else if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) { - m_state = TorrentState::CheckingResumeData; - } - else if (isMoveInProgress()) { - m_state = TorrentState::Moving; - } - else if (hasMissingFiles()) { - m_state = TorrentState::MissingFiles; - } - else if (isPaused()) { - m_state = isSeed() ? TorrentState::PausedUploading : TorrentState::PausedDownloading; - } - else if (m_session->isQueueingSystemEnabled() && isQueued() && !isChecking()) { - m_state = isSeed() ? TorrentState::QueuedUploading : TorrentState::QueuedDownloading; - } - else { - switch (m_nativeStatus.state) { - case lt::torrent_status::finished: - case lt::torrent_status::seeding: - if (isForced()) - m_state = TorrentState::ForcedUploading; - else - m_state = m_nativeStatus.upload_payload_rate > 0 ? TorrentState::Uploading : TorrentState::StalledUploading; - break; - case lt::torrent_status::allocating: - m_state = TorrentState::Allocating; - break; - case lt::torrent_status::checking_files: - m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading; - break; - case lt::torrent_status::downloading_metadata: - m_state = TorrentState::DownloadingMetadata; - break; - case lt::torrent_status::downloading: - if (isForced()) - m_state = TorrentState::ForcedDownloading; - else - m_state = m_nativeStatus.download_payload_rate > 0 ? TorrentState::Downloading : TorrentState::StalledDownloading; - break; - default: - qWarning("Unrecognized torrent status, should not happen!!! status was %d", m_nativeStatus.state); - m_state = TorrentState::Unknown; - } - } -} - -bool TorrentHandle::hasMetadata() const -{ - return m_nativeStatus.has_metadata; -} - -bool TorrentHandle::hasMissingFiles() const -{ - return m_hasMissingFiles; -} - -bool TorrentHandle::hasError() const -{ - return static_cast(m_nativeStatus.errc); -} - -bool TorrentHandle::hasFilteredPieces() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - const std::vector pp = m_nativeHandle.piece_priorities(); -#else - const std::vector pp = m_nativeHandle.get_piece_priorities(); -#endif - return std::any_of(pp.cbegin(), pp.cend(), [](const LTDownloadPriority priority) - { - return (priority == LTDownloadPriority {0}); - }); -} - -int TorrentHandle::queuePosition() const -{ - if (m_nativeStatus.queue_position < LTQueuePosition {0}) return 0; - - return static_cast(m_nativeStatus.queue_position) + 1; -} - -QString TorrentHandle::error() const -{ - return QString::fromStdString(m_nativeStatus.errc.message()); -} - -qlonglong TorrentHandle::totalDownload() const -{ - return m_nativeStatus.all_time_download; -} - -qlonglong TorrentHandle::totalUpload() const -{ - return m_nativeStatus.all_time_upload; -} - -qlonglong TorrentHandle::activeTime() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.active_time; -#else - return lt::total_seconds(m_nativeStatus.active_duration); -#endif -} - -qlonglong TorrentHandle::finishedTime() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.finished_time; -#else - return lt::total_seconds(m_nativeStatus.finished_duration); -#endif -} - -qlonglong TorrentHandle::seedingTime() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.seeding_time; -#else - return lt::total_seconds(m_nativeStatus.seeding_duration); -#endif -} - -qlonglong TorrentHandle::eta() const -{ - if (isPaused()) return MAX_ETA; - - const SpeedSampleAvg speedAverage = m_speedMonitor.average(); - - if (isSeed()) { - const qreal maxRatioValue = maxRatio(); - const int maxSeedingTimeValue = maxSeedingTime(); - if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA; - - qlonglong ratioEta = MAX_ETA; - - if ((speedAverage.upload > 0) && (maxRatioValue >= 0)) { - - qlonglong realDL = totalDownload(); - if (realDL <= 0) - realDL = wantedSize(); - - ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload; - } - - qlonglong seedingTimeEta = MAX_ETA; - - if (maxSeedingTimeValue >= 0) { - seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime(); - if (seedingTimeEta < 0) - seedingTimeEta = 0; - } - - return qMin(ratioEta, seedingTimeEta); - } - - if (!speedAverage.download) return MAX_ETA; - - return (wantedSize() - completedSize()) / speedAverage.download; -} - -QVector TorrentHandle::filesProgress() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - std::vector fp; -#else - std::vector fp; -#endif - m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); - - const int count = static_cast(fp.size()); - QVector result; - result.reserve(count); - for (int i = 0; i < count; ++i) { - const qlonglong size = fileSize(i); - if ((size <= 0) || (fp[i] == size)) - result << 1; - else - result << (fp[i] / static_cast(size)); - } - - return result; -} - -int TorrentHandle::seedsCount() const -{ - return m_nativeStatus.num_seeds; -} - -int TorrentHandle::peersCount() const -{ - return m_nativeStatus.num_peers; -} - -int TorrentHandle::leechsCount() const -{ - return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds); -} - -int TorrentHandle::totalSeedsCount() const -{ - return (m_nativeStatus.num_complete > 0) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds; -} - -int TorrentHandle::totalPeersCount() const -{ - const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete; - return (peers > 0) ? peers : m_nativeStatus.list_peers; -} - -int TorrentHandle::totalLeechersCount() const -{ - return (m_nativeStatus.num_incomplete > 0) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds); -} - -int TorrentHandle::completeCount() const -{ - // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646 - return m_nativeStatus.num_complete; -} - -int TorrentHandle::incompleteCount() const -{ - // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646 - return m_nativeStatus.num_incomplete; -} - -QDateTime TorrentHandle::lastSeenComplete() const -{ - if (m_nativeStatus.last_seen_complete > 0) - return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete); - else - return {}; -} - -QDateTime TorrentHandle::completedTime() const -{ - if (m_nativeStatus.completed_time > 0) - return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time); - else - return {}; -} - -qlonglong TorrentHandle::timeSinceUpload() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.time_since_upload; -#else - if (m_nativeStatus.last_upload.time_since_epoch().count() == 0) - return -1; - return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload); -#endif -} - -qlonglong TorrentHandle::timeSinceDownload() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.time_since_download; -#else - if (m_nativeStatus.last_download.time_since_epoch().count() == 0) - return -1; - return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download); -#endif -} - -qlonglong TorrentHandle::timeSinceActivity() const -{ - const qlonglong upTime = timeSinceUpload(); - const qlonglong downTime = timeSinceDownload(); - return ((upTime < 0) != (downTime < 0)) - ? std::max(upTime, downTime) - : std::min(upTime, downTime); -} - -int TorrentHandle::downloadLimit() const -{ - return m_nativeHandle.download_limit(); -} - -int TorrentHandle::uploadLimit() const -{ - return m_nativeHandle.upload_limit(); -} - -bool TorrentHandle::superSeeding() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - return m_nativeStatus.super_seeding; -#else - return bool {m_nativeStatus.flags & lt::torrent_flags::super_seeding}; -#endif -} - -QVector TorrentHandle::peers() const -{ - std::vector nativePeers; - m_nativeHandle.get_peer_info(nativePeers); - - QVector peers; - peers.reserve(nativePeers.size()); - for (const lt::peer_info &peer : nativePeers) - peers << PeerInfo(this, peer); - return peers; -} - -QBitArray TorrentHandle::pieces() const -{ - QBitArray result(m_nativeStatus.pieces.size()); - for (int i = 0; i < result.size(); ++i) { - if (m_nativeStatus.pieces[LTPieceIndex {i}]) - result.setBit(i, true); - } - return result; -} - -QBitArray TorrentHandle::downloadingPieces() const -{ - QBitArray result(piecesCount()); - - std::vector queue; - m_nativeHandle.get_download_queue(queue); - - for (const lt::partial_piece_info &info : queue) -#if (LIBTORRENT_VERSION_NUM < 10200) - result.setBit(info.piece_index); -#else - result.setBit(LTUnderlyingType {info.piece_index}); -#endif - - return result; -} - -QVector TorrentHandle::pieceAvailability() const -{ - std::vector avail; - m_nativeHandle.piece_availability(avail); - - return Vector::fromStdVector(avail); -} - -qreal TorrentHandle::distributedCopies() const -{ - return m_nativeStatus.distributed_copies; -} - -qreal TorrentHandle::maxRatio() const -{ - if (m_ratioLimit == USE_GLOBAL_RATIO) - return m_session->globalMaxRatio(); - - return m_ratioLimit; -} - -int TorrentHandle::maxSeedingTime() const -{ - if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME) - return m_session->globalMaxSeedingMinutes(); - - return m_seedingTimeLimit; -} - -qreal TorrentHandle::realRatio() const -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - const boost::int64_t upload = m_nativeStatus.all_time_upload; - // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent - const boost::int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01)) - ? m_nativeStatus.total_done - : m_nativeStatus.all_time_download; -#else - const int64_t upload = m_nativeStatus.all_time_upload; - // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent - const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01)) - ? m_nativeStatus.total_done - : m_nativeStatus.all_time_download; -#endif - - if (download == 0) - return (upload == 0) ? 0 : MAX_RATIO; - - const qreal ratio = upload / static_cast(download); - Q_ASSERT(ratio >= 0); - return (ratio > MAX_RATIO) ? MAX_RATIO : ratio; -} - -int TorrentHandle::uploadPayloadRate() const -{ - return m_nativeStatus.upload_payload_rate; -} - -int TorrentHandle::downloadPayloadRate() const -{ - return m_nativeStatus.download_payload_rate; -} - -qlonglong TorrentHandle::totalPayloadUpload() const -{ - return m_nativeStatus.total_payload_upload; -} - -qlonglong TorrentHandle::totalPayloadDownload() const -{ - return m_nativeStatus.total_payload_download; -} - -int TorrentHandle::connectionsCount() const -{ - return m_nativeStatus.num_connections; -} - -int TorrentHandle::connectionsLimit() const -{ - return m_nativeStatus.connections_limit; -} - -qlonglong TorrentHandle::nextAnnounce() const -{ - return lt::total_seconds(m_nativeStatus.next_announce); -} - -void TorrentHandle::setName(const QString &name) -{ - if (m_name != name) { - m_name = name; - m_session->handleTorrentNameChanged(this); - } -} - -bool TorrentHandle::setCategory(const QString &category) -{ - if (m_category != category) { - if (!category.isEmpty() && !m_session->categories().contains(category)) - return false; - - const QString oldCategory = m_category; - m_category = category; - m_session->handleTorrentCategoryChanged(this, oldCategory); - - if (m_useAutoTMM) { - if (!m_session->isDisableAutoTMMWhenCategoryChanged()) - move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); - else - setAutoTMMEnabled(false); - } - } - - return true; -} - -void TorrentHandle::move(QString path) -{ - if (m_useAutoTMM) { - m_useAutoTMM = false; - m_session->handleTorrentSavingModeChanged(this); - } - - path = Utils::Fs::toUniformPath(path.trimmed()); - if (path.isEmpty()) - path = m_session->defaultSavePath(); - if (!path.endsWith('/')) - path += '/'; - - move_impl(path, MoveStorageMode::KeepExistingFiles); -} - -void TorrentHandle::move_impl(QString path, const MoveStorageMode mode) -{ - if (path == savePath()) return; - path = Utils::Fs::toNativePath(path); - - if (!useTempPath()) - moveStorage(path, mode); - - m_savePath = path; - m_session->handleTorrentSavePathChanged(this); -} - -void TorrentHandle::forceReannounce(int index) -{ - m_nativeHandle.force_reannounce(0, index); -} - -void TorrentHandle::forceDHTAnnounce() -{ - m_nativeHandle.force_dht_announce(); -} - -void TorrentHandle::forceRecheck() -{ - if (!hasMetadata()) return; - - m_nativeHandle.force_recheck(); - m_unchecked = false; -} - -void TorrentHandle::setSequentialDownload(const bool enable) -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - m_nativeHandle.set_sequential_download(enable); - m_nativeStatus.sequential_download = enable; // prevent return cached value -#else - if (enable) { - m_nativeHandle.set_flags(lt::torrent_flags::sequential_download); - m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value - } - else { - m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download); - m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value - } -#endif - - saveResumeData(); -} - -void TorrentHandle::toggleSequentialDownload() -{ - setSequentialDownload(!isSequentialDownload()); -} - -void TorrentHandle::setFirstLastPiecePriority(const bool enabled) -{ - setFirstLastPiecePriorityImpl(enabled); -} - -void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVector &updatedFilePrio) -{ - // Download first and last pieces first for every file in the torrent - - if (!hasMetadata()) { - m_needsToSetFirstLastPiecePriority = enabled; - return; - } - -#if (LIBTORRENT_VERSION_NUM < 10200) - const std::vector filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio) - : nativeHandle().file_priorities(); - std::vector piecePriorities = nativeHandle().piece_priorities(); -#else - const std::vector filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio) - : nativeHandle().get_file_priorities(); - std::vector piecePriorities = nativeHandle().get_piece_priorities(); -#endif - // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it - // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case. - for (int index = 0; index < static_cast(filePriorities.size()); ++index) { - const LTDownloadPriority filePrio = filePriorities[index]; - if (filePrio <= LTDownloadPriority {0}) - continue; - - // Determine the priority to set - const LTDownloadPriority newPrio = enabled ? LTDownloadPriority {7} : filePrio; - const TorrentInfo::PieceRange extremities = info().filePieces(index); - - // worst case: AVI index = 1% of total file size (at the end of the file) - const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength()); - for (int i = 0; i < nNumPieces; ++i) { - piecePriorities[extremities.first() + i] = newPrio; - piecePriorities[extremities.last() - i] = newPrio; - } - } - - m_nativeHandle.prioritize_pieces(piecePriorities); - - LogMsg(tr("Download first and last piece first: %1, torrent: '%2'") - .arg((enabled ? tr("On") : tr("Off")), name())); - - saveResumeData(); -} - -void TorrentHandle::toggleFirstLastPiecePriority() -{ - setFirstLastPiecePriority(!hasFirstLastPiecePriority()); -} - -void TorrentHandle::pause() -{ - if (isPaused()) return; - - setAutoManaged(false); - m_nativeHandle.pause(); - - // Libtorrent doesn't emit a torrent_paused_alert when the - // torrent is queued (no I/O) - // We test on the cached m_nativeStatus - if (isQueued()) - m_session->handleTorrentPaused(this); -} - -void TorrentHandle::resume(bool forced) -{ - resume_impl(forced); -} - -void TorrentHandle::resume_impl(bool forced) -{ - if (hasError()) - m_nativeHandle.clear_error(); - - if (m_hasMissingFiles) { - m_hasMissingFiles = false; - m_nativeHandle.force_recheck(); - } - - setAutoManaged(!forced); - if (forced) - m_nativeHandle.resume(); -} - -void TorrentHandle::moveStorage(const QString &newPath, const MoveStorageMode mode) -{ - if (m_session->addMoveTorrentStorageJob(this, newPath, mode)) - m_storageIsMoving = true; -} - -void TorrentHandle::renameFile(const int index, const QString &name) -{ - m_oldPath[LTFileIndex {index}].push_back(filePath(index)); - ++m_renameCount; - m_nativeHandle.rename_file(LTFileIndex {index}, Utils::Fs::toNativePath(name).toStdString()); -} - -void TorrentHandle::handleStateUpdate(const lt::torrent_status &nativeStatus) -{ - updateStatus(nativeStatus); -} - -void TorrentHandle::handleStorageMoved(const QString &newPath, const QString &errorMessage) -{ - m_storageIsMoving = false; - - if (!errorMessage.isEmpty()) - LogMsg(tr("Could not move torrent: %1. Reason: %2").arg(name(), errorMessage), Log::CRITICAL); - else - LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), newPath)); - - updateStatus(); - saveResumeData(); - - while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) - m_moveFinishedTriggers.takeFirst()(); -} - -void TorrentHandle::handleTrackerReplyAlert(const lt::tracker_reply_alert *p) -{ - const QString trackerUrl(p->tracker_url()); - qDebug("Received a tracker reply from %s (Num_peers = %d)", qUtf8Printable(trackerUrl), p->num_peers); - // Connection was successful now. Remove possible old errors - m_trackerInfos[trackerUrl] = {{}, p->num_peers}; - - m_session->handleTorrentTrackerReply(this, trackerUrl); -} - -void TorrentHandle::handleTrackerWarningAlert(const lt::tracker_warning_alert *p) -{ - const QString trackerUrl = p->tracker_url(); - const QString message = p->warning_message(); - - // Connection was successful now but there is a warning message - m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message - - m_session->handleTorrentTrackerWarning(this, trackerUrl); -} - -void TorrentHandle::handleTrackerErrorAlert(const lt::tracker_error_alert *p) -{ - const QString trackerUrl = p->tracker_url(); - const QString message = p->error_message(); - - m_trackerInfos[trackerUrl].lastMessage = message; - - // Starting with libtorrent 1.2.x each tracker has multiple local endpoints from which - // an announce is attempted. Some endpoints might succeed while others might fail. - // Emit the signal only if all endpoints have failed. - const QVector trackerList = trackers(); - const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry) - { - return (entry.url() == trackerUrl); - }); - if ((iter != trackerList.cend()) && (iter->status() == TrackerEntry::NotWorking)) - m_session->handleTorrentTrackerError(this, trackerUrl); -} - -void TorrentHandle::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p) -{ - Q_UNUSED(p); - qDebug("\"%s\" have just finished checking", qUtf8Printable(name())); - - if (m_fastresumeDataRejected && !m_hasMissingFiles) { - saveResumeData(); - m_fastresumeDataRejected = false; - } - - updateStatus(); - - if (!m_hasMissingFiles) { - if ((progress() < 1.0) && (wantedSize() > 0)) - m_hasSeedStatus = false; - else if (progress() == 1.0) - m_hasSeedStatus = true; - - adjustActualSavePath(); - manageIncompleteFiles(); - } - - m_session->handleTorrentChecked(this); -} - -void TorrentHandle::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p) -{ - Q_UNUSED(p); - qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name())); - qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no"); - m_hasMissingFiles = false; - if (m_hasSeedStatus) return; - - updateStatus(); - m_hasSeedStatus = true; - - adjustActualSavePath(); - manageIncompleteFiles(); - - const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion(); - if (isMoveInProgress() || (m_renameCount > 0)) { - if (recheckTorrentsOnCompletion) - m_moveFinishedTriggers.append([this]() { forceRecheck(); }); - m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); }); - } - else { - if (recheckTorrentsOnCompletion && m_unchecked) - forceRecheck(); - m_session->handleTorrentFinished(this); - } -} - -void TorrentHandle::handleTorrentPausedAlert(const lt::torrent_paused_alert *p) -{ - Q_UNUSED(p); - - updateStatus(); - m_speedMonitor.reset(); - - m_session->handleTorrentPaused(this); -} - -void TorrentHandle::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p) -{ - Q_UNUSED(p); - - m_session->handleTorrentResumed(this); -} - -void TorrentHandle::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - const bool useDummyResumeData = !(p && p->resume_data); - auto resumeDataPtr = std::make_shared(useDummyResumeData - ? lt::entry {} - : *(p->resume_data)); -#else - const bool useDummyResumeData = !p; - auto resumeDataPtr = std::make_shared(useDummyResumeData - ? lt::entry {} - : lt::write_resume_data(p->params)); -#endif - lt::entry &resumeData = *resumeDataPtr; - - updateStatus(); - - if (useDummyResumeData) { - resumeData["qBt-magnetUri"] = toMagnetUri().toStdString(); - resumeData["paused"] = isPaused(); - resumeData["auto_managed"] = isAutoManaged(); - // Both firstLastPiecePriority and sequential need to be stored in the - // resume data if there is no metadata, otherwise they won't be - // restored if qBittorrent quits before the metadata are retrieved: - resumeData["qBt-firstLastPiecePriority"] = hasFirstLastPiecePriority(); - resumeData["qBt-sequential"] = isSequentialDownload(); - - resumeData["qBt-addedTime"] = addedTime().toSecsSinceEpoch(); - } - else { - const auto savePath = resumeData.find_key("save_path")->string(); - resumeData["save_path"] = Profile::instance()->toPortablePath(QString::fromStdString(savePath)).toStdString(); - } - resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance()->toPortablePath(m_savePath).toStdString(); - resumeData["qBt-ratioLimit"] = static_cast(m_ratioLimit * 1000); - resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit; - resumeData["qBt-category"] = m_category.toStdString(); - resumeData["qBt-tags"] = setToEntryList(m_tags); - resumeData["qBt-name"] = m_name.toStdString(); - resumeData["qBt-seedStatus"] = m_hasSeedStatus; - resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled; - resumeData["qBt-queuePosition"] = (static_cast(nativeHandle().queue_position()) + 1); // qBt starts queue at 1 - resumeData["qBt-hasRootFolder"] = m_hasRootFolder; - -#if (LIBTORRENT_VERSION_NUM < 10200) - if (m_nativeStatus.stop_when_ready) { -#else - if (m_nativeStatus.flags & lt::torrent_flags::stop_when_ready) { -#endif - // We need to redefine these values when torrent starting/rechecking - // in "paused" state since native values can be logically wrong - // (torrent can be not paused and auto_managed when it is checking). - resumeData["paused"] = true; - resumeData["auto_managed"] = false; - } - - m_session->handleTorrentResumeDataReady(this, resumeDataPtr); -} - -void TorrentHandle::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p) -{ - // if torrent has no metadata we should save dummy fastresume data - // containing Magnet URI and qBittorrent own resume data only - if (p->error.value() == lt::errors::no_metadata) { - handleSaveResumeDataAlert(nullptr); - } - else { - LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"") - .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL); - m_session->handleTorrentResumeDataFailed(this); - } -} - -void TorrentHandle::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p) -{ - m_fastresumeDataRejected = true; - - if (p->error.value() == lt::errors::mismatching_file_size) { - // Mismatching file size (files were probably moved) - m_hasMissingFiles = true; - LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL); - } - else { - LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...") - .arg(name(), QString::fromStdString(p->message())), Log::WARNING); - } -} - -void TorrentHandle::handleFileRenamedAlert(const lt::file_renamed_alert *p) -{ - // We don't really need to call updateStatus() in this place. - // All we need to do is make sure we have a valid instance of the TorrentInfo object. - m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; - - // remove empty leftover folders - // for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will - // be removed if they are empty - const QString oldFilePath = m_oldPath[p->index].takeFirst(); - const QString newFilePath = Utils::Fs::toUniformPath(p->new_name()); - - if (m_oldPath[p->index].isEmpty()) - m_oldPath.remove(p->index); - - QVector oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts); - oldPathParts.removeLast(); // drop file name part - QVector newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts); - newPathParts.removeLast(); // drop file name part - -#if defined(Q_OS_WIN) - const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - - int pathIdx = 0; - while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) { - if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0) - break; - ++pathIdx; - } - - for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) { - QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/"))); - oldPathParts.removeLast(); - } - - --m_renameCount; - while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) - m_moveFinishedTriggers.takeFirst()(); - - if (isPaused() && (m_renameCount == 0)) - saveResumeData(); // otherwise the new path will not be saved -} - -void TorrentHandle::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p) -{ - LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"") - .arg(name(), filePath(LTUnderlyingType {p->index}) - , QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING); - - m_oldPath[p->index].removeFirst(); - if (m_oldPath[p->index].isEmpty()) - m_oldPath.remove(p->index); - - --m_renameCount; - while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) - m_moveFinishedTriggers.takeFirst()(); - - if (isPaused() && (m_renameCount == 0)) - saveResumeData(); // otherwise the new path will not be saved -} - -void TorrentHandle::handleFileCompletedAlert(const lt::file_completed_alert *p) -{ - // We don't really need to call updateStatus() in this place. - // All we need to do is make sure we have a valid instance of the TorrentInfo object. - m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; - - qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name())); - if (m_session->isAppendExtensionEnabled()) { - QString name = filePath(LTUnderlyingType {p->index}); - if (name.endsWith(QB_EXT)) { - const QString oldName = name; - name.chop(QB_EXT.size()); - qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name)); - renameFile(LTUnderlyingType {p->index}, name); - } - } -} - -void TorrentHandle::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) -{ - Q_UNUSED(p); - qDebug("Metadata received for torrent %s.", qUtf8Printable(name())); - updateStatus(); - if (m_session->isAppendExtensionEnabled()) - manageIncompleteFiles(); - if (!m_hasRootFolder) - m_torrentInfo.stripRootFolder(); - if (filesCount() == 1) - m_hasRootFolder = false; - m_session->handleTorrentMetadataReceived(this); - - if (isPaused()) { - // XXX: Unfortunately libtorrent-rasterbar does not send a torrent_paused_alert - // and the torrent can be paused when metadata is received - m_speedMonitor.reset(); - m_session->handleTorrentPaused(this); - } - - // If first/last piece priority was specified when adding this torrent, we can set it - // now that we have metadata: - if (m_needsToSetFirstLastPiecePriority) { - setFirstLastPiecePriority(true); - m_needsToSetFirstLastPiecePriority = false; - } -} - -void TorrentHandle::handlePerformanceAlert(const lt::performance_alert *p) const -{ - LogMsg((tr("Performance alert: ") + QString::fromStdString(p->message())) - , Log::INFO); -} - -void TorrentHandle::handleTempPathChanged() -{ - adjustActualSavePath(); -} - -void TorrentHandle::handleCategorySavePathChanged() -{ - if (m_useAutoTMM) - move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); -} - -void TorrentHandle::handleAppendExtensionToggled() -{ - if (!hasMetadata()) return; - - manageIncompleteFiles(); -} - -void TorrentHandle::handleAlert(const lt::alert *a) -{ - switch (a->type()) { - case lt::file_renamed_alert::alert_type: - handleFileRenamedAlert(static_cast(a)); - break; - case lt::file_rename_failed_alert::alert_type: - handleFileRenameFailedAlert(static_cast(a)); - break; - case lt::file_completed_alert::alert_type: - handleFileCompletedAlert(static_cast(a)); - break; - case lt::torrent_finished_alert::alert_type: - handleTorrentFinishedAlert(static_cast(a)); - break; - case lt::save_resume_data_alert::alert_type: - handleSaveResumeDataAlert(static_cast(a)); - break; - case lt::save_resume_data_failed_alert::alert_type: - handleSaveResumeDataFailedAlert(static_cast(a)); - break; - case lt::torrent_paused_alert::alert_type: - handleTorrentPausedAlert(static_cast(a)); - break; - case lt::torrent_resumed_alert::alert_type: - handleTorrentResumedAlert(static_cast(a)); - break; - case lt::tracker_error_alert::alert_type: - handleTrackerErrorAlert(static_cast(a)); - break; - case lt::tracker_reply_alert::alert_type: - handleTrackerReplyAlert(static_cast(a)); - break; - case lt::tracker_warning_alert::alert_type: - handleTrackerWarningAlert(static_cast(a)); - break; - case lt::metadata_received_alert::alert_type: - handleMetadataReceivedAlert(static_cast(a)); - break; - case lt::fastresume_rejected_alert::alert_type: - handleFastResumeRejectedAlert(static_cast(a)); - break; - case lt::torrent_checked_alert::alert_type: - handleTorrentCheckedAlert(static_cast(a)); - break; - case lt::performance_alert::alert_type: - handlePerformanceAlert(static_cast(a)); - break; - } -} - -void TorrentHandle::manageIncompleteFiles() -{ - const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled(); - const QVector fp = filesProgress(); - if (fp.size() != filesCount()) { - qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress"; - return; - } - - for (int i = 0; i < filesCount(); ++i) { - QString name = filePath(i); - if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) { - if (!name.endsWith(QB_EXT)) { - const QString newName = name + QB_EXT; - qDebug() << "Renaming" << name << "to" << newName; - renameFile(i, newName); - } - } - else { - if (name.endsWith(QB_EXT)) { - const QString oldName = name; - name.chop(QB_EXT.size()); - qDebug() << "Renaming" << oldName << "to" << name; - renameFile(i, name); - } - } - } -} - -void TorrentHandle::adjustActualSavePath() -{ - if (!isMoveInProgress()) - adjustActualSavePath_impl(); - else - m_moveFinishedTriggers.append([this]() { adjustActualSavePath_impl(); }); -} - -void TorrentHandle::adjustActualSavePath_impl() -{ - const bool needUseTempDir = useTempPath(); - const QDir tempDir {m_session->torrentTempPath(info())}; - const QDir currentDir {actualStorageLocation()}; - const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}}; - - if (targetDir == currentDir) return; - - if (!needUseTempDir) { - if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()})) { - // torrent without root folder still has it in its temporary save path - // so its temp path isn't equal to temp path root - const QString currentDirPath = currentDir.absolutePath(); - m_moveFinishedTriggers.append([currentDirPath] - { - qDebug() << "Removing torrent temp folder:" << currentDirPath; - Utils::Fs::smartRemoveEmptyFolderTree(currentDirPath); - }); - } - } - - moveStorage(Utils::Fs::toNativePath(targetDir.absolutePath()), MoveStorageMode::Overwrite); -} - -lt::torrent_handle TorrentHandle::nativeHandle() const -{ - return m_nativeHandle; -} - -void TorrentHandle::updateTorrentInfo() -{ - if (!hasMetadata()) return; - - m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file.lock()); -} - -bool TorrentHandle::isMoveInProgress() const -{ - return m_storageIsMoving; -} - -bool TorrentHandle::useTempPath() const -{ - return !m_tempPathDisabled && m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus); -} - -void TorrentHandle::updateStatus() -{ - updateStatus(m_nativeHandle.status()); -} - -void TorrentHandle::updateStatus(const lt::torrent_status &nativeStatus) -{ - m_nativeStatus = nativeStatus; - - updateState(); - updateTorrentInfo(); - - // NOTE: Don't change the order of these conditionals! - // Otherwise it will not work properly since torrent can be CheckingDownloading. - if (isChecking()) - m_unchecked = false; - else if (isDownloading()) - m_unchecked = true; - - m_speedMonitor.addSample({nativeStatus.download_payload_rate - , nativeStatus.upload_payload_rate}); -} - -void TorrentHandle::setRatioLimit(qreal limit) -{ - if (limit < USE_GLOBAL_RATIO) - limit = NO_RATIO_LIMIT; - else if (limit > MAX_RATIO) - limit = MAX_RATIO; - - if (m_ratioLimit != limit) { - m_ratioLimit = limit; - m_session->handleTorrentShareLimitChanged(this); - } -} - -void TorrentHandle::setSeedingTimeLimit(int limit) -{ - if (limit < USE_GLOBAL_SEEDING_TIME) - limit = NO_SEEDING_TIME_LIMIT; - else if (limit > MAX_SEEDING_TIME) - limit = MAX_SEEDING_TIME; - - if (m_seedingTimeLimit != limit) { - m_seedingTimeLimit = limit; - m_session->handleTorrentShareLimitChanged(this); - } -} - -void TorrentHandle::setUploadLimit(const int limit) -{ - m_nativeHandle.set_upload_limit(limit); -} - -void TorrentHandle::setDownloadLimit(const int limit) -{ - m_nativeHandle.set_download_limit(limit); -} - -void TorrentHandle::setSuperSeeding(const bool enable) -{ -#if (LIBTORRENT_VERSION_NUM < 10200) - m_nativeHandle.super_seeding(enable); -#else - if (enable) - m_nativeHandle.set_flags(lt::torrent_flags::super_seeding); - else - m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding); -#endif -} - -void TorrentHandle::flushCache() const -{ - m_nativeHandle.flush_cache(); -} - -QString TorrentHandle::toMagnetUri() const -{ - return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle)); -} - -void TorrentHandle::prioritizeFiles(const QVector &priorities) -{ - if (!hasMetadata()) return; - if (priorities.size() != filesCount()) return; - - // Save first/last piece first option state - const bool firstLastPieceFirst = hasFirstLastPiecePriority(); - - // Reset 'm_hasSeedStatus' if needed in order to react again to - // 'torrent_finished_alert' and eg show tray notifications - const QVector progress = filesProgress(); - const QVector oldPriorities = filePriorities(); - for (int i = 0; i < oldPriorities.size(); ++i) { - if ((oldPriorities[i] == DownloadPriority::Ignored) - && (priorities[i] > DownloadPriority::Ignored) - && (progress[i] < 1.0)) { - m_hasSeedStatus = false; - break; - } - } - - qDebug() << Q_FUNC_INFO << "Changing files priorities..."; - m_nativeHandle.prioritize_files(toLTDownloadPriorities(priorities)); - - qDebug() << Q_FUNC_INFO << "Moving unwanted files to .unwanted folder and conversely..."; - const QString spath = savePath(true); - for (int i = 0; i < priorities.size(); ++i) { - const QString filepath = filePath(i); - // Move unwanted files to a .unwanted subfolder - if (priorities[i] == DownloadPriority::Ignored) { - const QString oldAbsPath = QDir(spath).absoluteFilePath(filepath); - const QString parentAbsPath = Utils::Fs::branchPath(oldAbsPath); - // Make sure the file does not already exists - if (QDir(parentAbsPath).dirName() != ".unwanted") { - const QString unwantedAbsPath = parentAbsPath + "/.unwanted"; - const QString newAbsPath = unwantedAbsPath + '/' + Utils::Fs::fileName(filepath); - qDebug() << "Unwanted path is" << unwantedAbsPath; - if (QFile::exists(newAbsPath)) { - qWarning() << "File" << newAbsPath << "already exists at destination."; - continue; - } - - const bool created = QDir().mkpath(unwantedAbsPath); - qDebug() << "unwanted folder was created:" << created; -#ifdef Q_OS_WIN - if (created) { - // Hide the folder on Windows - qDebug() << "Hiding folder (Windows)"; - std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString(); - DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str()); - bool ret = ::SetFileAttributesW(winPath.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN); - Q_ASSERT(ret != 0); Q_UNUSED(ret); - } -#endif - QString parentPath = Utils::Fs::branchPath(filepath); - if (!parentPath.isEmpty() && !parentPath.endsWith('/')) - parentPath += '/'; - renameFile(i, parentPath + ".unwanted/" + Utils::Fs::fileName(filepath)); - } - } - - // Move wanted files back to their original folder - if (priorities[i] > DownloadPriority::Ignored) { - const QString parentRelPath = Utils::Fs::branchPath(filepath); - if (QDir(parentRelPath).dirName() == ".unwanted") { - const QString oldName = Utils::Fs::fileName(filepath); - const QString newRelPath = Utils::Fs::branchPath(parentRelPath); - if (newRelPath.isEmpty()) - renameFile(i, oldName); - else - renameFile(i, QDir(newRelPath).filePath(oldName)); - - // Remove .unwanted directory if empty - qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted"); - QDir(spath + '/' + newRelPath).rmdir(".unwanted"); - } - } - } - - // Restore first/last piece first option if necessary - if (firstLastPieceFirst) - setFirstLastPiecePriorityImpl(true, priorities); -} - -QVector TorrentHandle::availableFileFractions() const -{ - const int filesCount = this->filesCount(); - if (filesCount < 0) return {}; - - const QVector piecesAvailability = pieceAvailability(); - // libtorrent returns empty array for seeding only torrents - if (piecesAvailability.empty()) return QVector(filesCount, -1.); - - QVector res; - res.reserve(filesCount); - const TorrentInfo info = this->info(); - for (int i = 0; i < filesCount; ++i) { - const TorrentInfo::PieceRange filePieces = info.filePieces(i); - - int availablePieces = 0; - for (int piece = filePieces.first(); piece <= filePieces.last(); ++piece) { - availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0; - } - res.push_back(static_cast(availablePieces) / filePieces.size()); + setFirstLastPiecePriority(!hasFirstLastPiecePriority()); } - return res; } diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 559bdb3d5..811e527b9 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -27,84 +27,30 @@ * exception statement from your version. */ -#ifndef BITTORRENT_TORRENTHANDLE_H -#define BITTORRENT_TORRENTHANDLE_H +#pragma once -#include - -#include -#include -#include - -#include #include -#include -#include +#include #include #include #include -#include "private/speedmonitor.h" -#include "infohash.h" -#include "torrentinfo.h" - -extern const QString QB_EXT; - class QBitArray; class QDateTime; class QStringList; class QUrl; +extern const QString QB_EXT; + namespace BitTorrent { enum class DownloadPriority; + class InfoHash; class PeerInfo; - class Session; + class TorrentInfo; class TrackerEntry; - struct AddTorrentParams; struct PeerAddress; - struct CreateTorrentParams - { - bool restored; // is existing torrent job? - // for both new and restored torrents - QString name; - QString category; - QSet tags; - QString savePath; - bool disableTempPath; - bool sequential; - bool firstLastPiecePriority; - bool hasSeedStatus; - bool skipChecking; - bool hasRootFolder; - bool forced; - bool paused; - int uploadLimit; - int downloadLimit; - // for new torrents - QVector filePriorities; - QDateTime addedTime; - // for restored torrents - qreal ratioLimit; - int seedingTimeLimit; - - CreateTorrentParams(); - explicit CreateTorrentParams(const AddTorrentParams ¶ms); - }; - - struct TrackerInfo - { - QString lastMessage; - int numPeers = 0; - }; - - enum class MoveStorageMode - { - KeepExistingFiles, - Overwrite - }; - enum class TorrentState { Unknown = -1, @@ -135,13 +81,16 @@ namespace BitTorrent Error }; + struct TrackerInfo + { + QString lastMessage; + int numPeers = 0; + }; + uint qHash(TorrentState key, uint seed); - class TorrentHandle : public QObject + class TorrentHandle { - Q_DISABLE_COPY(TorrentHandle) - Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandle) - public: static const qreal USE_GLOBAL_RATIO; static const qreal NO_RATIO_LIMIT; @@ -152,24 +101,21 @@ namespace BitTorrent static const qreal MAX_RATIO; static const int MAX_SEEDING_TIME; - TorrentHandle(Session *session, const lt::torrent_handle &nativeHandle, - const CreateTorrentParams ¶ms); - ~TorrentHandle(); - - bool isValid() const; - InfoHash hash() const; - QString name() const; - QDateTime creationDate() const; - QString creator() const; - QString comment() const; - bool isPrivate() const; - qlonglong totalSize() const; - qlonglong wantedSize() const; - qlonglong completedSize() const; - qlonglong incompletedSize() const; - qlonglong pieceLength() const; - qlonglong wastedSize() const; - QString currentTracker() const; + virtual ~TorrentHandle() = default; + + virtual InfoHash hash() const = 0; + virtual QString name() const = 0; + virtual QDateTime creationDate() const = 0; + virtual QString creator() const = 0; + virtual QString comment() const = 0; + virtual bool isPrivate() const = 0; + virtual qlonglong totalSize() const = 0; + virtual qlonglong wantedSize() const = 0; + virtual qlonglong completedSize() const = 0; + virtual qlonglong incompletedSize() const = 0; + virtual qlonglong pieceLength() const = 0; + virtual qlonglong wastedSize() const = 0; + virtual QString currentTracker() const = 0; // 1. savePath() - the path where all the files and subfolders of torrent are stored (as always). // 2. rootPath() - absolute path of torrent file tree (save path + first item from 1st torrent file path). @@ -214,237 +160,140 @@ namespace BitTorrent // | B | /home/user/torrents/torrentB | /home/user/torrents/torrentB/subdir1/file1 | // | C | /home/user/torrents/file1 | /home/user/torrents/file1 | - QString savePath(bool actual = false) const; - QString rootPath(bool actual = false) const; - QString contentPath(bool actual = false) const; - - bool useTempPath() const; - - bool isAutoTMMEnabled() const; - void setAutoTMMEnabled(bool enabled); - QString category() const; - bool belongsToCategory(const QString &category) const; - bool setCategory(const QString &category); - - QSet tags() const; - bool hasTag(const QString &tag) const; - bool addTag(const QString &tag); - bool removeTag(const QString &tag); - void removeAllTags(); - - bool hasRootFolder() const; - - int filesCount() const; - int piecesCount() const; - int piecesHave() const; - qreal progress() const; - QDateTime addedTime() const; - qreal ratioLimit() const; - int seedingTimeLimit() const; - - QString filePath(int index) const; - QString fileName(int index) const; - qlonglong fileSize(int index) const; - QStringList absoluteFilePaths() const; - QStringList absoluteFilePathsUnwanted() const; - QVector filePriorities() const; - - TorrentInfo info() const; - bool isSeed() const; - bool isPaused() const; - bool isResumed() const; - bool isQueued() const; - bool isForced() const; - bool isChecking() const; - bool isDownloading() const; - bool isUploading() const; - bool isCompleted() const; - bool isActive() const; - bool isInactive() const; - bool isErrored() const; - bool isSequentialDownload() const; - bool hasFirstLastPiecePriority() const; - TorrentState state() const; - bool hasMetadata() const; - bool hasMissingFiles() const; - bool hasError() const; - bool hasFilteredPieces() const; - int queuePosition() const; - QVector trackers() const; - QHash trackerInfos() const; - QVector urlSeeds() const; - QString error() const; - qlonglong totalDownload() const; - qlonglong totalUpload() const; - qlonglong activeTime() const; - qlonglong finishedTime() const; - qlonglong seedingTime() const; - qlonglong eta() const; - QVector filesProgress() const; - int seedsCount() const; - int peersCount() const; - int leechsCount() const; - int totalSeedsCount() const; - int totalPeersCount() const; - int totalLeechersCount() const; - int completeCount() const; - int incompleteCount() const; - QDateTime lastSeenComplete() const; - QDateTime completedTime() const; - qlonglong timeSinceUpload() const; - qlonglong timeSinceDownload() const; - qlonglong timeSinceActivity() const; - int downloadLimit() const; - int uploadLimit() const; - bool superSeeding() const; - QVector peers() const; - QBitArray pieces() const; - QBitArray downloadingPieces() const; - QVector pieceAvailability() const; - qreal distributedCopies() const; - qreal maxRatio() const; - int maxSeedingTime() const; - qreal realRatio() const; - int uploadPayloadRate() const; - int downloadPayloadRate() const; - qlonglong totalPayloadUpload() const; - qlonglong totalPayloadDownload() const; - int connectionsCount() const; - int connectionsLimit() const; - qlonglong nextAnnounce() const; - - void setName(const QString &name); - void setSequentialDownload(bool enable); - void toggleSequentialDownload(); - void setFirstLastPiecePriority(bool enabled); - void toggleFirstLastPiecePriority(); - void pause(); - void resume(bool forced = false); - void move(QString path); - void forceReannounce(int index = -1); - void forceDHTAnnounce(); - void forceRecheck(); - void renameFile(int index, const QString &name); - void prioritizeFiles(const QVector &priorities); - void setRatioLimit(qreal limit); - void setSeedingTimeLimit(int limit); - void setUploadLimit(int limit); - void setDownloadLimit(int limit); - void setSuperSeeding(bool enable); - void flushCache() const; - void addTrackers(const QVector &trackers); - void replaceTrackers(const QVector &trackers); - void addUrlSeeds(const QVector &urlSeeds); - void removeUrlSeeds(const QVector &urlSeeds); - bool connectPeer(const PeerAddress &peerAddress); - - QString toMagnetUri() const; - - bool needSaveResumeData() const; - - // Session interface - lt::torrent_handle nativeHandle() const; - - void handleAlert(const lt::alert *a); - void handleStateUpdate(const lt::torrent_status &nativeStatus); - void handleTempPathChanged(); - void handleCategorySavePathChanged(); - void handleAppendExtensionToggled(); - void saveResumeData(); - void handleStorageMoved(const QString &newPath, const QString &errorMessage); - + virtual QString savePath(bool actual = false) const = 0; + virtual QString rootPath(bool actual = false) const = 0; + virtual QString contentPath(bool actual = false) const = 0; + + virtual bool useTempPath() const = 0; + + virtual bool isAutoTMMEnabled() const = 0; + virtual void setAutoTMMEnabled(bool enabled) = 0; + virtual QString category() const = 0; + virtual bool belongsToCategory(const QString &category) const = 0; + virtual bool setCategory(const QString &category) = 0; + + virtual QSet tags() const = 0; + virtual bool hasTag(const QString &tag) const = 0; + virtual bool addTag(const QString &tag) = 0; + virtual bool removeTag(const QString &tag) = 0; + virtual void removeAllTags() = 0; + + virtual bool hasRootFolder() const = 0; + + virtual int filesCount() const = 0; + virtual int piecesCount() const = 0; + virtual int piecesHave() const = 0; + virtual qreal progress() const = 0; + virtual QDateTime addedTime() const = 0; + virtual qreal ratioLimit() const = 0; + virtual int seedingTimeLimit() const = 0; + + virtual QString filePath(int index) const = 0; + virtual QString fileName(int index) const = 0; + virtual qlonglong fileSize(int index) const = 0; + virtual QStringList absoluteFilePaths() const = 0; + virtual QStringList absoluteFilePathsUnwanted() const = 0; + virtual QVector filePriorities() const = 0; + + virtual TorrentInfo info() const = 0; + virtual bool isSeed() const = 0; + virtual bool isPaused() const = 0; + virtual bool isResumed() const = 0; + virtual bool isQueued() const = 0; + virtual bool isForced() const = 0; + virtual bool isChecking() const = 0; + virtual bool isDownloading() const = 0; + virtual bool isUploading() const = 0; + virtual bool isCompleted() const = 0; + virtual bool isActive() const = 0; + virtual bool isInactive() const = 0; + virtual bool isErrored() const = 0; + virtual bool isSequentialDownload() const = 0; + virtual bool hasFirstLastPiecePriority() const = 0; + virtual TorrentState state() const = 0; + virtual bool hasMetadata() const = 0; + virtual bool hasMissingFiles() const = 0; + virtual bool hasError() const = 0; + virtual bool hasFilteredPieces() const = 0; + virtual int queuePosition() const = 0; + virtual QVector trackers() const = 0; + virtual QHash trackerInfos() const = 0; + virtual QVector urlSeeds() const = 0; + virtual QString error() const = 0; + virtual qlonglong totalDownload() const = 0; + virtual qlonglong totalUpload() const = 0; + virtual qlonglong activeTime() const = 0; + virtual qlonglong finishedTime() const = 0; + virtual qlonglong seedingTime() const = 0; + virtual qlonglong eta() const = 0; + virtual QVector filesProgress() const = 0; + virtual int seedsCount() const = 0; + virtual int peersCount() const = 0; + virtual int leechsCount() const = 0; + virtual int totalSeedsCount() const = 0; + virtual int totalPeersCount() const = 0; + virtual int totalLeechersCount() const = 0; + virtual int completeCount() const = 0; + virtual int incompleteCount() const = 0; + virtual QDateTime lastSeenComplete() const = 0; + virtual QDateTime completedTime() const = 0; + virtual qlonglong timeSinceUpload() const = 0; + virtual qlonglong timeSinceDownload() const = 0; + virtual qlonglong timeSinceActivity() const = 0; + virtual int downloadLimit() const = 0; + virtual int uploadLimit() const = 0; + virtual bool superSeeding() const = 0; + virtual QVector peers() const = 0; + virtual QBitArray pieces() const = 0; + virtual QBitArray downloadingPieces() const = 0; + virtual QVector pieceAvailability() const = 0; + virtual qreal distributedCopies() const = 0; + virtual qreal maxRatio() const = 0; + virtual int maxSeedingTime() const = 0; + virtual qreal realRatio() const = 0; + virtual int uploadPayloadRate() const = 0; + virtual int downloadPayloadRate() const = 0; + virtual qlonglong totalPayloadUpload() const = 0; + virtual qlonglong totalPayloadDownload() const = 0; + virtual int connectionsCount() const = 0; + virtual int connectionsLimit() const = 0; + virtual qlonglong nextAnnounce() const = 0; /** * @brief fraction of file pieces that are available at least from one peer * * This is not the same as torrrent availability, it is just a fraction of pieces * that can be downloaded right now. It varies between 0 to 1. */ - QVector availableFileFractions() const; - - private: - typedef std::function EventTrigger; - -#if (LIBTORRENT_VERSION_NUM < 10200) - using LTFileIndex = int; -#else - using LTFileIndex = lt::file_index_t; -#endif - - void updateStatus(); - void updateStatus(const lt::torrent_status &nativeStatus); - void updateState(); - void updateTorrentInfo(); - - void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p); - void handleFileCompletedAlert(const lt::file_completed_alert *p); - void handleFileRenamedAlert(const lt::file_renamed_alert *p); - void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p); - void handleMetadataReceivedAlert(const lt::metadata_received_alert *p); - void handlePerformanceAlert(const lt::performance_alert *p) const; - void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p); - void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p); - void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p); - void handleTorrentFinishedAlert(const lt::torrent_finished_alert *p); - void handleTorrentPausedAlert(const lt::torrent_paused_alert *p); - void handleTorrentResumedAlert(const lt::torrent_resumed_alert *p); - void handleTrackerErrorAlert(const lt::tracker_error_alert *p); - void handleTrackerReplyAlert(const lt::tracker_reply_alert *p); - void handleTrackerWarningAlert(const lt::tracker_warning_alert *p); - - void resume_impl(bool forced); - bool isMoveInProgress() const; - QString actualStorageLocation() const; - bool isAutoManaged() const; - void setAutoManaged(bool enable); - - void adjustActualSavePath(); - void adjustActualSavePath_impl(); - void move_impl(QString path, MoveStorageMode mode); - void moveStorage(const QString &newPath, MoveStorageMode mode); - void manageIncompleteFiles(); - void setFirstLastPiecePriorityImpl(bool enabled, const QVector &updatedFilePrio = {}); - - Session *const m_session; - lt::torrent_handle m_nativeHandle; - lt::torrent_status m_nativeStatus; - TorrentState m_state; - TorrentInfo m_torrentInfo; - SpeedMonitor m_speedMonitor; - - InfoHash m_hash; - - bool m_storageIsMoving = false; - // m_moveFinishedTriggers is activated only when the following conditions are met: - // all file rename jobs complete, all file move jobs complete - QQueue m_moveFinishedTriggers; - int m_renameCount; - - // Until libtorrent provide an "old_name" field in `file_renamed_alert` - // we will rely on this workaround to remove empty leftover folders - QHash> m_oldPath; - - bool m_useAutoTMM; - - // Persistent data - QString m_name; - QString m_savePath; - QString m_category; - QSet m_tags; - bool m_hasSeedStatus; - qreal m_ratioLimit; - int m_seedingTimeLimit; - bool m_tempPathDisabled; - bool m_fastresumeDataRejected; - bool m_hasMissingFiles; - bool m_hasRootFolder; - bool m_needsToSetFirstLastPiecePriority; - - QHash m_trackerInfos; - - bool m_unchecked = false; + virtual QVector availableFileFractions() const = 0; + + virtual void setName(const QString &name) = 0; + virtual void setSequentialDownload(bool enable) = 0; + virtual void setFirstLastPiecePriority(bool enabled) = 0; + virtual void pause() = 0; + virtual void resume(bool forced = false) = 0; + virtual void move(QString path) = 0; + virtual void forceReannounce(int index = -1) = 0; + virtual void forceDHTAnnounce() = 0; + virtual void forceRecheck() = 0; + virtual void renameFile(int index, const QString &name) = 0; + virtual void prioritizeFiles(const QVector &priorities) = 0; + virtual void setRatioLimit(qreal limit) = 0; + virtual void setSeedingTimeLimit(int limit) = 0; + virtual void setUploadLimit(int limit) = 0; + virtual void setDownloadLimit(int limit) = 0; + virtual void setSuperSeeding(bool enable) = 0; + virtual void flushCache() const = 0; + virtual void addTrackers(const QVector &trackers) = 0; + virtual void replaceTrackers(const QVector &trackers) = 0; + virtual void addUrlSeeds(const QVector &urlSeeds) = 0; + virtual void removeUrlSeeds(const QVector &urlSeeds) = 0; + virtual bool connectPeer(const PeerAddress &peerAddress) = 0; + + virtual QString createMagnetURI() const = 0; + + void toggleSequentialDownload(); + void toggleFirstLastPiecePriority(); }; } Q_DECLARE_METATYPE(BitTorrent::TorrentState) - -#endif // BITTORRENT_TORRENTHANDLE_H diff --git a/src/base/bittorrent/torrenthandleimpl.cpp b/src/base/bittorrent/torrenthandleimpl.cpp new file mode 100644 index 000000000..4b7e56509 --- /dev/null +++ b/src/base/bittorrent/torrenthandleimpl.cpp @@ -0,0 +1,2147 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "torrenthandleimpl.h" + +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +#include +#include +#include +#include +#include +#include + +#if (LIBTORRENT_VERSION_NUM >= 10200) +#include +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "base/global.h" +#include "base/logger.h" +#include "base/preferences.h" +#include "base/profile.h" +#include "base/tristatebool.h" +#include "base/utils/fs.h" +#include "base/utils/string.h" +#include "downloadpriority.h" +#include "peeraddress.h" +#include "peerinfo.h" +#include "private/ltunderlyingtype.h" +#include "session.h" +#include "trackerentry.h" + +using namespace BitTorrent; + +#if (LIBTORRENT_VERSION_NUM >= 10200) +namespace libtorrent +{ + namespace aux + { + template + uint qHash(const strong_typedef &key, const uint seed) + { + return ::qHash((std::hash> {})(key), seed); + } + } +} +#endif + +namespace +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + using LTDownloadPriority = int; + using LTPieceIndex = int; + using LTQueuePosition = int; +#else + using LTDownloadPriority = lt::download_priority_t; + using LTPieceIndex = lt::piece_index_t; + using LTQueuePosition = lt::queue_position_t; +#endif + + std::vector toLTDownloadPriorities(const QVector &priorities) + { + std::vector out; + out.reserve(priorities.size()); + + std::transform(priorities.cbegin(), priorities.cend() + , std::back_inserter(out), [](BitTorrent::DownloadPriority priority) + { + return static_cast( + static_cast>(priority)); + }); + return out; + } + + using ListType = lt::entry::list_type; + + ListType setToEntryList(const QSet &input) + { + ListType entryList; + for (const QString &setValue : input) + entryList.emplace_back(setValue.toStdString()); + return entryList; + } +} + +// CreateTorrentParams + +CreateTorrentParams::CreateTorrentParams(const AddTorrentParams ¶ms) + : name(params.name) + , category(params.category) + , tags(params.tags) + , savePath(params.savePath) + , disableTempPath(params.disableTempPath) + , sequential(params.sequential) + , firstLastPiecePriority(params.firstLastPiecePriority) + , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping + , skipChecking(params.skipChecking) + , hasRootFolder(params.createSubfolder == TriStateBool::Undefined + ? Session::instance()->isCreateTorrentSubfolder() + : params.createSubfolder == TriStateBool::True) + , forced(params.addForced == TriStateBool::True) + , paused(params.addPaused == TriStateBool::Undefined + ? Session::instance()->isAddTorrentPaused() + : params.addPaused == TriStateBool::True) + , uploadLimit(params.uploadLimit) + , downloadLimit(params.downloadLimit) + , filePriorities(params.filePriorities) + , ratioLimit(params.ignoreShareLimits ? TorrentHandleImpl::NO_RATIO_LIMIT : TorrentHandleImpl::USE_GLOBAL_RATIO) + , seedingTimeLimit(params.ignoreShareLimits ? TorrentHandleImpl::NO_SEEDING_TIME_LIMIT : TorrentHandleImpl::USE_GLOBAL_SEEDING_TIME) +{ + bool useAutoTMM = (params.useAutoTMM == TriStateBool::Undefined + ? !Session::instance()->isAutoTMMDisabledByDefault() + : params.useAutoTMM == TriStateBool::True); + if (useAutoTMM) + savePath = ""; + else if (savePath.trimmed().isEmpty()) + savePath = Session::instance()->defaultSavePath(); +} + +// TorrentHandleImpl + +TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle &nativeHandle, + const CreateTorrentParams ¶ms) + : QObject(session) + , m_session(session) + , m_nativeHandle(nativeHandle) + , m_state(TorrentState::Unknown) + , m_renameCount(0) + , m_useAutoTMM(params.savePath.isEmpty()) + , m_name(params.name) + , m_savePath(Utils::Fs::toNativePath(params.savePath)) + , m_category(params.category) + , m_tags(params.tags) + , m_hasSeedStatus(params.hasSeedStatus) + , m_ratioLimit(params.ratioLimit) + , m_seedingTimeLimit(params.seedingTimeLimit) + , m_tempPathDisabled(params.disableTempPath) + , m_fastresumeDataRejected(false) + , m_hasMissingFiles(false) + , m_hasRootFolder(params.hasRootFolder) + , m_needsToSetFirstLastPiecePriority(false) +{ + if (m_useAutoTMM) + m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category)); + + updateStatus(); + m_hash = InfoHash(m_nativeStatus.info_hash); + + // NB: the following two if statements are present because we don't want + // to set either sequential download or first/last piece priority to false + // if their respective flags in data are false when a torrent is being + // resumed. This is because, in that circumstance, this constructor is + // called with those flags set to false, even if the torrent was set to + // download sequentially or have first/last piece priority enabled when + // its resume data was saved. These two settings are restored later. But + // if we set them to false now, both will erroneously not be restored. + if (!params.restored || params.sequential) + setSequentialDownload(params.sequential); + if (!params.restored || params.firstLastPiecePriority) + setFirstLastPiecePriority(params.firstLastPiecePriority); + + if (!params.restored && hasMetadata()) { + if (filesCount() == 1) + m_hasRootFolder = false; + } +} + +TorrentHandleImpl::~TorrentHandleImpl() {} + +bool TorrentHandleImpl::isValid() const +{ + return m_nativeHandle.is_valid(); +} + +InfoHash TorrentHandleImpl::hash() const +{ + return m_hash; +} + +QString TorrentHandleImpl::name() const +{ + QString name = m_name; + if (!name.isEmpty()) return name; + + name = QString::fromStdString(m_nativeStatus.name); + if (!name.isEmpty()) return name; + + if (hasMetadata()) { + name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name()); + if (!name.isEmpty()) return name; + } + + return m_hash; +} + +QDateTime TorrentHandleImpl::creationDate() const +{ + return m_torrentInfo.creationDate(); +} + +QString TorrentHandleImpl::creator() const +{ + return m_torrentInfo.creator(); +} + +QString TorrentHandleImpl::comment() const +{ + return m_torrentInfo.comment(); +} + +bool TorrentHandleImpl::isPrivate() const +{ + return m_torrentInfo.isPrivate(); +} + +qlonglong TorrentHandleImpl::totalSize() const +{ + return m_torrentInfo.totalSize(); +} + +// size without the "don't download" files +qlonglong TorrentHandleImpl::wantedSize() const +{ + return m_nativeStatus.total_wanted; +} + +qlonglong TorrentHandleImpl::completedSize() const +{ + return m_nativeStatus.total_wanted_done; +} + +qlonglong TorrentHandleImpl::incompletedSize() const +{ + return (m_nativeStatus.total_wanted - m_nativeStatus.total_wanted_done); +} + +qlonglong TorrentHandleImpl::pieceLength() const +{ + return m_torrentInfo.pieceLength(); +} + +qlonglong TorrentHandleImpl::wastedSize() const +{ + return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes); +} + +QString TorrentHandleImpl::currentTracker() const +{ + return QString::fromStdString(m_nativeStatus.current_tracker); +} + +QString TorrentHandleImpl::savePath(bool actual) const +{ + if (actual) + return Utils::Fs::toUniformPath(actualStorageLocation()); + else + return Utils::Fs::toUniformPath(m_savePath); +} + +QString TorrentHandleImpl::rootPath(bool actual) const +{ + if ((filesCount() > 1) && !hasRootFolder()) + return {}; + + const QString firstFilePath = filePath(0); + const int slashIndex = firstFilePath.indexOf('/'); + if (slashIndex >= 0) + return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex)); + else + return QDir(savePath(actual)).absoluteFilePath(firstFilePath); +} + +QString TorrentHandleImpl::contentPath(const bool actual) const +{ + if (filesCount() == 1) + return QDir(savePath(actual)).absoluteFilePath(filePath(0)); + + if (hasRootFolder()) + return rootPath(actual); + + return savePath(actual); +} + +bool TorrentHandleImpl::isAutoTMMEnabled() const +{ + return m_useAutoTMM; +} + +void TorrentHandleImpl::setAutoTMMEnabled(bool enabled) +{ + if (m_useAutoTMM == enabled) return; + + m_useAutoTMM = enabled; + m_session->handleTorrentSavingModeChanged(this); + + if (m_useAutoTMM) + move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); +} + +bool TorrentHandleImpl::hasRootFolder() const +{ + return m_hasRootFolder; +} + +QString TorrentHandleImpl::actualStorageLocation() const +{ + return QString::fromStdString(m_nativeStatus.save_path); +} + +bool TorrentHandleImpl::isAutoManaged() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.auto_managed; +#else + return bool {m_nativeStatus.flags & lt::torrent_flags::auto_managed}; +#endif +} + +void TorrentHandleImpl::setAutoManaged(const bool enable) +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + m_nativeHandle.auto_managed(enable); +#else + if (enable) + m_nativeHandle.set_flags(lt::torrent_flags::auto_managed); + else + m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed); +#endif +} + +QVector TorrentHandleImpl::trackers() const +{ + const std::vector nativeTrackers = m_nativeHandle.trackers(); + + QVector entries; + entries.reserve(nativeTrackers.size()); + + for (const lt::announce_entry &tracker : nativeTrackers) + entries << tracker; + + return entries; +} + +QHash TorrentHandleImpl::trackerInfos() const +{ + return m_trackerInfos; +} + +void TorrentHandleImpl::addTrackers(const QVector &trackers) +{ + QSet currentTrackers; + for (const lt::announce_entry &entry : m_nativeHandle.trackers()) + currentTrackers << entry; + + QVector newTrackers; + newTrackers.reserve(trackers.size()); + + for (const TrackerEntry &tracker : trackers) { + if (!currentTrackers.contains(tracker)) { + m_nativeHandle.add_tracker(tracker.nativeEntry()); + newTrackers << tracker; + } + } + + if (!newTrackers.isEmpty()) + m_session->handleTorrentTrackersAdded(this, newTrackers); +} + +void TorrentHandleImpl::replaceTrackers(const QVector &trackers) +{ + QVector currentTrackers = this->trackers(); + + QVector newTrackers; + newTrackers.reserve(trackers.size()); + + std::vector nativeTrackers; + nativeTrackers.reserve(trackers.size()); + + for (const TrackerEntry &tracker : trackers) { + nativeTrackers.emplace_back(tracker.nativeEntry()); + + if (!currentTrackers.removeOne(tracker)) + newTrackers << tracker; + } + + m_nativeHandle.replace_trackers(nativeTrackers); + + if (newTrackers.isEmpty() && currentTrackers.isEmpty()) { + // when existing tracker reorders + m_session->handleTorrentTrackersChanged(this); + } + else { + if (!currentTrackers.isEmpty()) + m_session->handleTorrentTrackersRemoved(this, currentTrackers); + + if (!newTrackers.isEmpty()) + m_session->handleTorrentTrackersAdded(this, newTrackers); + } +} + +QVector TorrentHandleImpl::urlSeeds() const +{ + const std::set currentSeeds = m_nativeHandle.url_seeds(); + + QVector urlSeeds; + urlSeeds.reserve(currentSeeds.size()); + + for (const std::string &urlSeed : currentSeeds) + urlSeeds.append(QUrl(urlSeed.c_str())); + + return urlSeeds; +} + +void TorrentHandleImpl::addUrlSeeds(const QVector &urlSeeds) +{ + const std::set currentSeeds = m_nativeHandle.url_seeds(); + + QVector addedUrlSeeds; + addedUrlSeeds.reserve(urlSeeds.size()); + + for (const QUrl &url : urlSeeds) { + const std::string nativeUrl = url.toString().toStdString(); + if (currentSeeds.find(nativeUrl) == currentSeeds.end()) { + m_nativeHandle.add_url_seed(nativeUrl); + addedUrlSeeds << url; + } + } + + if (!addedUrlSeeds.isEmpty()) + m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds); +} + +void TorrentHandleImpl::removeUrlSeeds(const QVector &urlSeeds) +{ + const std::set currentSeeds = m_nativeHandle.url_seeds(); + + QVector removedUrlSeeds; + removedUrlSeeds.reserve(urlSeeds.size()); + + for (const QUrl &url : urlSeeds) { + const std::string nativeUrl = url.toString().toStdString(); + if (currentSeeds.find(nativeUrl) != currentSeeds.end()) { + m_nativeHandle.remove_url_seed(nativeUrl); + removedUrlSeeds << url; + } + } + + if (!removedUrlSeeds.isEmpty()) + m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds); +} + +bool TorrentHandleImpl::connectPeer(const PeerAddress &peerAddress) +{ + lt::error_code ec; +#if (LIBTORRENT_VERSION_NUM < 10200) + const lt::address addr = lt::address::from_string(peerAddress.ip.toString().toStdString(), ec); +#else + const lt::address addr = lt::make_address(peerAddress.ip.toString().toStdString(), ec); +#endif + if (ec) return false; + + const lt::tcp::endpoint endpoint(addr, peerAddress.port); + try { + m_nativeHandle.connect_peer(endpoint); + } +#if (LIBTORRENT_VERSION_NUM < 10200) + catch (const boost::system::system_error &err) { +#else + catch (const lt::system_error &err) { +#endif + LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3") + .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING); + return false; + } + + LogMsg(tr("Peer \"%1\" is added to torrent \"%2\"").arg(peerAddress.toString(), name())); + return true; +} + +bool TorrentHandleImpl::needSaveResumeData() const +{ + return m_nativeHandle.need_save_resume_data(); +} + +void TorrentHandleImpl::saveResumeData() +{ + m_nativeHandle.save_resume_data(); + m_session->handleTorrentSaveResumeDataRequested(this); +} + +int TorrentHandleImpl::filesCount() const +{ + return m_torrentInfo.filesCount(); +} + +int TorrentHandleImpl::piecesCount() const +{ + return m_torrentInfo.piecesCount(); +} + +int TorrentHandleImpl::piecesHave() const +{ + return m_nativeStatus.num_pieces; +} + +qreal TorrentHandleImpl::progress() const +{ + if (!isChecking()) { + if (!m_nativeStatus.total_wanted) + return 0.; + + if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted) + return 1.; + + const qreal progress = static_cast(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted; + Q_ASSERT((progress >= 0.f) && (progress <= 1.f)); + return progress; + } + + return m_nativeStatus.progress; +} + +QString TorrentHandleImpl::category() const +{ + return m_category; +} + +bool TorrentHandleImpl::belongsToCategory(const QString &category) const +{ + if (m_category.isEmpty()) return category.isEmpty(); + if (!Session::isValidCategoryName(category)) return false; + + if (m_category == category) return true; + + if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/')) + return true; + + return false; +} + +QSet TorrentHandleImpl::tags() const +{ + return m_tags; +} + +bool TorrentHandleImpl::hasTag(const QString &tag) const +{ + return m_tags.contains(tag); +} + +bool TorrentHandleImpl::addTag(const QString &tag) +{ + if (!Session::isValidTag(tag)) + return false; + + if (!hasTag(tag)) { + if (!m_session->hasTag(tag)) + if (!m_session->addTag(tag)) + return false; + m_tags.insert(tag); + m_session->handleTorrentTagAdded(this, tag); + return true; + } + return false; +} + +bool TorrentHandleImpl::removeTag(const QString &tag) +{ + if (m_tags.remove(tag)) { + m_session->handleTorrentTagRemoved(this, tag); + return true; + } + return false; +} + +void TorrentHandleImpl::removeAllTags() +{ + for (const QString &tag : asConst(tags())) + removeTag(tag); +} + +QDateTime TorrentHandleImpl::addedTime() const +{ + return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time); +} + +qreal TorrentHandleImpl::ratioLimit() const +{ + return m_ratioLimit; +} + +int TorrentHandleImpl::seedingTimeLimit() const +{ + return m_seedingTimeLimit; +} + +QString TorrentHandleImpl::filePath(int index) const +{ + return m_torrentInfo.filePath(index); +} + +QString TorrentHandleImpl::fileName(int index) const +{ + if (!hasMetadata()) return {}; + return Utils::Fs::fileName(filePath(index)); +} + +qlonglong TorrentHandleImpl::fileSize(int index) const +{ + return m_torrentInfo.fileSize(index); +} + +// Return a list of absolute paths corresponding +// to all files in a torrent +QStringList TorrentHandleImpl::absoluteFilePaths() const +{ + if (!hasMetadata()) return {}; + + const QDir saveDir(savePath(true)); + QStringList res; + for (int i = 0; i < filesCount(); ++i) + res << Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i))); + return res; +} + +QStringList TorrentHandleImpl::absoluteFilePathsUnwanted() const +{ + if (!hasMetadata()) return {}; + + const QDir saveDir(savePath(true)); +#if (LIBTORRENT_VERSION_NUM < 10200) + const std::vector fp = m_nativeHandle.file_priorities(); +#else + const std::vector fp = m_nativeHandle.get_file_priorities(); +#endif + + QStringList res; + for (int i = 0; i < static_cast(fp.size()); ++i) { + if (fp[i] == LTDownloadPriority {0}) { + const QString path = Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i))); + if (path.contains(".unwanted")) + res << path; + } + } + + return res; +} + +QVector TorrentHandleImpl::filePriorities() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + const std::vector fp = m_nativeHandle.file_priorities(); +#else + const std::vector fp = m_nativeHandle.get_file_priorities(); +#endif + + QVector ret; + std::transform(fp.cbegin(), fp.cend(), std::back_inserter(ret), [](LTDownloadPriority priority) + { + return static_cast(LTUnderlyingType {priority}); + }); + return ret; +} + +TorrentInfo TorrentHandleImpl::info() const +{ + return m_torrentInfo; +} + +bool TorrentHandleImpl::isPaused() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return (m_nativeStatus.paused && !isAutoManaged()); +#else + return ((m_nativeStatus.flags & lt::torrent_flags::paused) + && !isAutoManaged()); +#endif +} + +bool TorrentHandleImpl::isResumed() const +{ + return !isPaused(); +} + +bool TorrentHandleImpl::isQueued() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return (m_nativeStatus.paused && isAutoManaged()); +#else + return ((m_nativeStatus.flags & lt::torrent_flags::paused) + && isAutoManaged()); +#endif +} + +bool TorrentHandleImpl::isChecking() const +{ + return ((m_nativeStatus.state == lt::torrent_status::checking_files) + || (m_nativeStatus.state == lt::torrent_status::checking_resume_data)); +} + +bool TorrentHandleImpl::isDownloading() const +{ + return m_state == TorrentState::Downloading + || m_state == TorrentState::DownloadingMetadata + || m_state == TorrentState::StalledDownloading + || m_state == TorrentState::CheckingDownloading + || m_state == TorrentState::PausedDownloading + || m_state == TorrentState::QueuedDownloading + || m_state == TorrentState::ForcedDownloading; +} + +bool TorrentHandleImpl::isUploading() const +{ + return m_state == TorrentState::Uploading + || m_state == TorrentState::StalledUploading + || m_state == TorrentState::CheckingUploading + || m_state == TorrentState::QueuedUploading + || m_state == TorrentState::ForcedUploading; +} + +bool TorrentHandleImpl::isCompleted() const +{ + return m_state == TorrentState::Uploading + || m_state == TorrentState::StalledUploading + || m_state == TorrentState::CheckingUploading + || m_state == TorrentState::PausedUploading + || m_state == TorrentState::QueuedUploading + || m_state == TorrentState::ForcedUploading; +} + +bool TorrentHandleImpl::isActive() const +{ + if (m_state == TorrentState::StalledDownloading) + return (uploadPayloadRate() > 0); + + return m_state == TorrentState::DownloadingMetadata + || m_state == TorrentState::Downloading + || m_state == TorrentState::ForcedDownloading + || m_state == TorrentState::Uploading + || m_state == TorrentState::ForcedUploading + || m_state == TorrentState::Moving; +} + +bool TorrentHandleImpl::isInactive() const +{ + return !isActive(); +} + +bool TorrentHandleImpl::isErrored() const +{ + return m_state == TorrentState::MissingFiles + || m_state == TorrentState::Error; +} + +bool TorrentHandleImpl::isSeed() const +{ + // Affected by bug http://code.rasterbar.com/libtorrent/ticket/402 + //bool result; + //result = m_nativeHandle.is_seed()); + //return result; + // May suffer from approximation problems + //return (progress() == 1.); + // This looks safe + return ((m_nativeStatus.state == lt::torrent_status::finished) + || (m_nativeStatus.state == lt::torrent_status::seeding)); +} + +bool TorrentHandleImpl::isForced() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return (!m_nativeStatus.paused && !isAutoManaged()); +#else + return (!(m_nativeStatus.flags & lt::torrent_flags::paused) + && !isAutoManaged()); +#endif +} + +bool TorrentHandleImpl::isSequentialDownload() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.sequential_download; +#else + return bool {m_nativeStatus.flags & lt::torrent_flags::sequential_download}; +#endif +} + +bool TorrentHandleImpl::hasFirstLastPiecePriority() const +{ + if (!hasMetadata()) + return m_needsToSetFirstLastPiecePriority; + +#if (LIBTORRENT_VERSION_NUM < 10200) + const std::vector filePriorities = nativeHandle().file_priorities(); +#else + const std::vector filePriorities = nativeHandle().get_file_priorities(); +#endif + for (int i = 0; i < static_cast(filePriorities.size()); ++i) { + if (filePriorities[i] <= LTDownloadPriority {0}) + continue; + + const TorrentInfo::PieceRange extremities = info().filePieces(i); + const LTDownloadPriority firstPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.first()}); + const LTDownloadPriority lastPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.last()}); + return ((firstPiecePrio == LTDownloadPriority {7}) && (lastPiecePrio == LTDownloadPriority {7})); + } + + return false; +} + +TorrentState TorrentHandleImpl::state() const +{ + return m_state; +} + +void TorrentHandleImpl::updateState() +{ + if (hasError()) { + m_state = TorrentState::Error; + } + else if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) { + m_state = TorrentState::CheckingResumeData; + } + else if (isMoveInProgress()) { + m_state = TorrentState::Moving; + } + else if (hasMissingFiles()) { + m_state = TorrentState::MissingFiles; + } + else if (isPaused()) { + m_state = isSeed() ? TorrentState::PausedUploading : TorrentState::PausedDownloading; + } + else if (m_session->isQueueingSystemEnabled() && isQueued() && !isChecking()) { + m_state = isSeed() ? TorrentState::QueuedUploading : TorrentState::QueuedDownloading; + } + else { + switch (m_nativeStatus.state) { + case lt::torrent_status::finished: + case lt::torrent_status::seeding: + if (isForced()) + m_state = TorrentState::ForcedUploading; + else + m_state = m_nativeStatus.upload_payload_rate > 0 ? TorrentState::Uploading : TorrentState::StalledUploading; + break; + case lt::torrent_status::allocating: + m_state = TorrentState::Allocating; + break; + case lt::torrent_status::checking_files: + m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading; + break; + case lt::torrent_status::downloading_metadata: + m_state = TorrentState::DownloadingMetadata; + break; + case lt::torrent_status::downloading: + if (isForced()) + m_state = TorrentState::ForcedDownloading; + else + m_state = m_nativeStatus.download_payload_rate > 0 ? TorrentState::Downloading : TorrentState::StalledDownloading; + break; + default: + qWarning("Unrecognized torrent status, should not happen!!! status was %d", m_nativeStatus.state); + m_state = TorrentState::Unknown; + } + } +} + +bool TorrentHandleImpl::hasMetadata() const +{ + return m_nativeStatus.has_metadata; +} + +bool TorrentHandleImpl::hasMissingFiles() const +{ + return m_hasMissingFiles; +} + +bool TorrentHandleImpl::hasError() const +{ + return static_cast(m_nativeStatus.errc); +} + +bool TorrentHandleImpl::hasFilteredPieces() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + const std::vector pp = m_nativeHandle.piece_priorities(); +#else + const std::vector pp = m_nativeHandle.get_piece_priorities(); +#endif + return std::any_of(pp.cbegin(), pp.cend(), [](const LTDownloadPriority priority) + { + return (priority == LTDownloadPriority {0}); + }); +} + +int TorrentHandleImpl::queuePosition() const +{ + if (m_nativeStatus.queue_position < LTQueuePosition {0}) return 0; + + return static_cast(m_nativeStatus.queue_position) + 1; +} + +QString TorrentHandleImpl::error() const +{ + return QString::fromStdString(m_nativeStatus.errc.message()); +} + +qlonglong TorrentHandleImpl::totalDownload() const +{ + return m_nativeStatus.all_time_download; +} + +qlonglong TorrentHandleImpl::totalUpload() const +{ + return m_nativeStatus.all_time_upload; +} + +qlonglong TorrentHandleImpl::activeTime() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.active_time; +#else + return lt::total_seconds(m_nativeStatus.active_duration); +#endif +} + +qlonglong TorrentHandleImpl::finishedTime() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.finished_time; +#else + return lt::total_seconds(m_nativeStatus.finished_duration); +#endif +} + +qlonglong TorrentHandleImpl::seedingTime() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.seeding_time; +#else + return lt::total_seconds(m_nativeStatus.seeding_duration); +#endif +} + +qlonglong TorrentHandleImpl::eta() const +{ + if (isPaused()) return MAX_ETA; + + const SpeedSampleAvg speedAverage = m_speedMonitor.average(); + + if (isSeed()) { + const qreal maxRatioValue = maxRatio(); + const int maxSeedingTimeValue = maxSeedingTime(); + if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA; + + qlonglong ratioEta = MAX_ETA; + + if ((speedAverage.upload > 0) && (maxRatioValue >= 0)) { + + qlonglong realDL = totalDownload(); + if (realDL <= 0) + realDL = wantedSize(); + + ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload; + } + + qlonglong seedingTimeEta = MAX_ETA; + + if (maxSeedingTimeValue >= 0) { + seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime(); + if (seedingTimeEta < 0) + seedingTimeEta = 0; + } + + return qMin(ratioEta, seedingTimeEta); + } + + if (!speedAverage.download) return MAX_ETA; + + return (wantedSize() - completedSize()) / speedAverage.download; +} + +QVector TorrentHandleImpl::filesProgress() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + std::vector fp; +#else + std::vector fp; +#endif + m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity); + + const int count = static_cast(fp.size()); + QVector result; + result.reserve(count); + for (int i = 0; i < count; ++i) { + const qlonglong size = fileSize(i); + if ((size <= 0) || (fp[i] == size)) + result << 1; + else + result << (fp[i] / static_cast(size)); + } + + return result; +} + +int TorrentHandleImpl::seedsCount() const +{ + return m_nativeStatus.num_seeds; +} + +int TorrentHandleImpl::peersCount() const +{ + return m_nativeStatus.num_peers; +} + +int TorrentHandleImpl::leechsCount() const +{ + return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds); +} + +int TorrentHandleImpl::totalSeedsCount() const +{ + return (m_nativeStatus.num_complete > 0) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds; +} + +int TorrentHandleImpl::totalPeersCount() const +{ + const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete; + return (peers > 0) ? peers : m_nativeStatus.list_peers; +} + +int TorrentHandleImpl::totalLeechersCount() const +{ + return (m_nativeStatus.num_incomplete > 0) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds); +} + +int TorrentHandleImpl::completeCount() const +{ + // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646 + return m_nativeStatus.num_complete; +} + +int TorrentHandleImpl::incompleteCount() const +{ + // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646 + return m_nativeStatus.num_incomplete; +} + +QDateTime TorrentHandleImpl::lastSeenComplete() const +{ + if (m_nativeStatus.last_seen_complete > 0) + return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete); + else + return {}; +} + +QDateTime TorrentHandleImpl::completedTime() const +{ + if (m_nativeStatus.completed_time > 0) + return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time); + else + return {}; +} + +qlonglong TorrentHandleImpl::timeSinceUpload() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.time_since_upload; +#else + if (m_nativeStatus.last_upload.time_since_epoch().count() == 0) + return -1; + return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload); +#endif +} + +qlonglong TorrentHandleImpl::timeSinceDownload() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.time_since_download; +#else + if (m_nativeStatus.last_download.time_since_epoch().count() == 0) + return -1; + return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download); +#endif +} + +qlonglong TorrentHandleImpl::timeSinceActivity() const +{ + const qlonglong upTime = timeSinceUpload(); + const qlonglong downTime = timeSinceDownload(); + return ((upTime < 0) != (downTime < 0)) + ? std::max(upTime, downTime) + : std::min(upTime, downTime); +} + +int TorrentHandleImpl::downloadLimit() const +{ + return m_nativeHandle.download_limit(); +} + +int TorrentHandleImpl::uploadLimit() const +{ + return m_nativeHandle.upload_limit(); +} + +bool TorrentHandleImpl::superSeeding() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + return m_nativeStatus.super_seeding; +#else + return bool {m_nativeStatus.flags & lt::torrent_flags::super_seeding}; +#endif +} + +QVector TorrentHandleImpl::peers() const +{ + std::vector nativePeers; + m_nativeHandle.get_peer_info(nativePeers); + + QVector peers; + peers.reserve(nativePeers.size()); + for (const lt::peer_info &peer : nativePeers) + peers << PeerInfo(this, peer); + return peers; +} + +QBitArray TorrentHandleImpl::pieces() const +{ + QBitArray result(m_nativeStatus.pieces.size()); + for (int i = 0; i < result.size(); ++i) { + if (m_nativeStatus.pieces[LTPieceIndex {i}]) + result.setBit(i, true); + } + return result; +} + +QBitArray TorrentHandleImpl::downloadingPieces() const +{ + QBitArray result(piecesCount()); + + std::vector queue; + m_nativeHandle.get_download_queue(queue); + + for (const lt::partial_piece_info &info : queue) +#if (LIBTORRENT_VERSION_NUM < 10200) + result.setBit(info.piece_index); +#else + result.setBit(LTUnderlyingType {info.piece_index}); +#endif + + return result; +} + +QVector TorrentHandleImpl::pieceAvailability() const +{ + std::vector avail; + m_nativeHandle.piece_availability(avail); + + return Vector::fromStdVector(avail); +} + +qreal TorrentHandleImpl::distributedCopies() const +{ + return m_nativeStatus.distributed_copies; +} + +qreal TorrentHandleImpl::maxRatio() const +{ + if (m_ratioLimit == USE_GLOBAL_RATIO) + return m_session->globalMaxRatio(); + + return m_ratioLimit; +} + +int TorrentHandleImpl::maxSeedingTime() const +{ + if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME) + return m_session->globalMaxSeedingMinutes(); + + return m_seedingTimeLimit; +} + +qreal TorrentHandleImpl::realRatio() const +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + const boost::int64_t upload = m_nativeStatus.all_time_upload; + // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent + const boost::int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01)) + ? m_nativeStatus.total_done + : m_nativeStatus.all_time_download; +#else + const int64_t upload = m_nativeStatus.all_time_upload; + // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent + const int64_t download = (m_nativeStatus.all_time_download < (m_nativeStatus.total_done * 0.01)) + ? m_nativeStatus.total_done + : m_nativeStatus.all_time_download; +#endif + + if (download == 0) + return (upload == 0) ? 0 : MAX_RATIO; + + const qreal ratio = upload / static_cast(download); + Q_ASSERT(ratio >= 0); + return (ratio > MAX_RATIO) ? MAX_RATIO : ratio; +} + +int TorrentHandleImpl::uploadPayloadRate() const +{ + return m_nativeStatus.upload_payload_rate; +} + +int TorrentHandleImpl::downloadPayloadRate() const +{ + return m_nativeStatus.download_payload_rate; +} + +qlonglong TorrentHandleImpl::totalPayloadUpload() const +{ + return m_nativeStatus.total_payload_upload; +} + +qlonglong TorrentHandleImpl::totalPayloadDownload() const +{ + return m_nativeStatus.total_payload_download; +} + +int TorrentHandleImpl::connectionsCount() const +{ + return m_nativeStatus.num_connections; +} + +int TorrentHandleImpl::connectionsLimit() const +{ + return m_nativeStatus.connections_limit; +} + +qlonglong TorrentHandleImpl::nextAnnounce() const +{ + return lt::total_seconds(m_nativeStatus.next_announce); +} + +void TorrentHandleImpl::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + m_session->handleTorrentNameChanged(this); + } +} + +bool TorrentHandleImpl::setCategory(const QString &category) +{ + if (m_category != category) { + if (!category.isEmpty() && !m_session->categories().contains(category)) + return false; + + const QString oldCategory = m_category; + m_category = category; + m_session->handleTorrentCategoryChanged(this, oldCategory); + + if (m_useAutoTMM) { + if (!m_session->isDisableAutoTMMWhenCategoryChanged()) + move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); + else + setAutoTMMEnabled(false); + } + } + + return true; +} + +void TorrentHandleImpl::move(QString path) +{ + if (m_useAutoTMM) { + m_useAutoTMM = false; + m_session->handleTorrentSavingModeChanged(this); + } + + path = Utils::Fs::toUniformPath(path.trimmed()); + if (path.isEmpty()) + path = m_session->defaultSavePath(); + if (!path.endsWith('/')) + path += '/'; + + move_impl(path, MoveStorageMode::KeepExistingFiles); +} + +void TorrentHandleImpl::move_impl(QString path, const MoveStorageMode mode) +{ + if (path == savePath()) return; + path = Utils::Fs::toNativePath(path); + + if (!useTempPath()) + moveStorage(path, mode); + + m_savePath = path; + m_session->handleTorrentSavePathChanged(this); +} + +void TorrentHandleImpl::forceReannounce(int index) +{ + m_nativeHandle.force_reannounce(0, index); +} + +void TorrentHandleImpl::forceDHTAnnounce() +{ + m_nativeHandle.force_dht_announce(); +} + +void TorrentHandleImpl::forceRecheck() +{ + if (!hasMetadata()) return; + + m_nativeHandle.force_recheck(); + m_unchecked = false; +} + +void TorrentHandleImpl::setSequentialDownload(const bool enable) +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + m_nativeHandle.set_sequential_download(enable); + m_nativeStatus.sequential_download = enable; // prevent return cached value +#else + if (enable) { + m_nativeHandle.set_flags(lt::torrent_flags::sequential_download); + m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value + } + else { + m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download); + m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value + } +#endif + + saveResumeData(); +} + +void TorrentHandleImpl::setFirstLastPiecePriority(const bool enabled) +{ + setFirstLastPiecePriorityImpl(enabled); +} + +void TorrentHandleImpl::setFirstLastPiecePriorityImpl(const bool enabled, const QVector &updatedFilePrio) +{ + // Download first and last pieces first for every file in the torrent + + if (!hasMetadata()) { + m_needsToSetFirstLastPiecePriority = enabled; + return; + } + +#if (LIBTORRENT_VERSION_NUM < 10200) + const std::vector filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio) + : nativeHandle().file_priorities(); + std::vector piecePriorities = nativeHandle().piece_priorities(); +#else + const std::vector filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio) + : nativeHandle().get_file_priorities(); + std::vector piecePriorities = nativeHandle().get_piece_priorities(); +#endif + // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it + // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case. + for (int index = 0; index < static_cast(filePriorities.size()); ++index) { + const LTDownloadPriority filePrio = filePriorities[index]; + if (filePrio <= LTDownloadPriority {0}) + continue; + + // Determine the priority to set + const LTDownloadPriority newPrio = enabled ? LTDownloadPriority {7} : filePrio; + const TorrentInfo::PieceRange extremities = info().filePieces(index); + + // worst case: AVI index = 1% of total file size (at the end of the file) + const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength()); + for (int i = 0; i < nNumPieces; ++i) { + piecePriorities[extremities.first() + i] = newPrio; + piecePriorities[extremities.last() - i] = newPrio; + } + } + + m_nativeHandle.prioritize_pieces(piecePriorities); + + LogMsg(tr("Download first and last piece first: %1, torrent: '%2'") + .arg((enabled ? tr("On") : tr("Off")), name())); + + saveResumeData(); +} + +void TorrentHandleImpl::pause() +{ + if (isPaused()) return; + + setAutoManaged(false); + m_nativeHandle.pause(); + + // Libtorrent doesn't emit a torrent_paused_alert when the + // torrent is queued (no I/O) + // We test on the cached m_nativeStatus + if (isQueued()) + m_session->handleTorrentPaused(this); +} + +void TorrentHandleImpl::resume(bool forced) +{ + resume_impl(forced); +} + +void TorrentHandleImpl::resume_impl(bool forced) +{ + if (hasError()) + m_nativeHandle.clear_error(); + + if (m_hasMissingFiles) { + m_hasMissingFiles = false; + m_nativeHandle.force_recheck(); + } + + setAutoManaged(!forced); + if (forced) + m_nativeHandle.resume(); +} + +void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMode mode) +{ + if (m_session->addMoveTorrentStorageJob(this, newPath, mode)) + m_storageIsMoving = true; +} + +void TorrentHandleImpl::renameFile(const int index, const QString &name) +{ + m_oldPath[LTFileIndex {index}].push_back(filePath(index)); + ++m_renameCount; + m_nativeHandle.rename_file(LTFileIndex {index}, Utils::Fs::toNativePath(name).toStdString()); +} + +void TorrentHandleImpl::handleStateUpdate(const lt::torrent_status &nativeStatus) +{ + updateStatus(nativeStatus); +} + +void TorrentHandleImpl::handleStorageMoved(const QString &newPath, const QString &errorMessage) +{ + m_storageIsMoving = false; + + if (!errorMessage.isEmpty()) + LogMsg(tr("Could not move torrent: %1. Reason: %2").arg(name(), errorMessage), Log::CRITICAL); + else + LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), newPath)); + + updateStatus(); + saveResumeData(); + + while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) + m_moveFinishedTriggers.takeFirst()(); +} + +void TorrentHandleImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p) +{ + const QString trackerUrl(p->tracker_url()); + qDebug("Received a tracker reply from %s (Num_peers = %d)", qUtf8Printable(trackerUrl), p->num_peers); + // Connection was successful now. Remove possible old errors + m_trackerInfos[trackerUrl] = {{}, p->num_peers}; + + m_session->handleTorrentTrackerReply(this, trackerUrl); +} + +void TorrentHandleImpl::handleTrackerWarningAlert(const lt::tracker_warning_alert *p) +{ + const QString trackerUrl = p->tracker_url(); + const QString message = p->warning_message(); + + // Connection was successful now but there is a warning message + m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message + + m_session->handleTorrentTrackerWarning(this, trackerUrl); +} + +void TorrentHandleImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p) +{ + const QString trackerUrl = p->tracker_url(); + const QString message = p->error_message(); + + m_trackerInfos[trackerUrl].lastMessage = message; + + // Starting with libtorrent 1.2.x each tracker has multiple local endpoints from which + // an announce is attempted. Some endpoints might succeed while others might fail. + // Emit the signal only if all endpoints have failed. + const QVector trackerList = trackers(); + const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry) + { + return (entry.url() == trackerUrl); + }); + if ((iter != trackerList.cend()) && (iter->status() == TrackerEntry::NotWorking)) + m_session->handleTorrentTrackerError(this, trackerUrl); +} + +void TorrentHandleImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p) +{ + Q_UNUSED(p); + qDebug("\"%s\" have just finished checking", qUtf8Printable(name())); + + if (m_fastresumeDataRejected && !m_hasMissingFiles) { + saveResumeData(); + m_fastresumeDataRejected = false; + } + + updateStatus(); + + if (!m_hasMissingFiles) { + if ((progress() < 1.0) && (wantedSize() > 0)) + m_hasSeedStatus = false; + else if (progress() == 1.0) + m_hasSeedStatus = true; + + adjustActualSavePath(); + manageIncompleteFiles(); + } + + m_session->handleTorrentChecked(this); +} + +void TorrentHandleImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p) +{ + Q_UNUSED(p); + qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name())); + qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no"); + m_hasMissingFiles = false; + if (m_hasSeedStatus) return; + + updateStatus(); + m_hasSeedStatus = true; + + adjustActualSavePath(); + manageIncompleteFiles(); + + const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion(); + if (isMoveInProgress() || (m_renameCount > 0)) { + if (recheckTorrentsOnCompletion) + m_moveFinishedTriggers.append([this]() { forceRecheck(); }); + m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); }); + } + else { + if (recheckTorrentsOnCompletion && m_unchecked) + forceRecheck(); + m_session->handleTorrentFinished(this); + } +} + +void TorrentHandleImpl::handleTorrentPausedAlert(const lt::torrent_paused_alert *p) +{ + Q_UNUSED(p); + + updateStatus(); + m_speedMonitor.reset(); + + m_session->handleTorrentPaused(this); +} + +void TorrentHandleImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p) +{ + Q_UNUSED(p); + + m_session->handleTorrentResumed(this); +} + +void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + const bool useDummyResumeData = !(p && p->resume_data); + auto resumeDataPtr = std::make_shared(useDummyResumeData + ? lt::entry {} + : *(p->resume_data)); +#else + const bool useDummyResumeData = !p; + auto resumeDataPtr = std::make_shared(useDummyResumeData + ? lt::entry {} + : lt::write_resume_data(p->params)); +#endif + lt::entry &resumeData = *resumeDataPtr; + + updateStatus(); + + if (useDummyResumeData) { + resumeData["qBt-magnetUri"] = createMagnetURI().toStdString(); + resumeData["paused"] = isPaused(); + resumeData["auto_managed"] = isAutoManaged(); + // Both firstLastPiecePriority and sequential need to be stored in the + // resume data if there is no metadata, otherwise they won't be + // restored if qBittorrent quits before the metadata are retrieved: + resumeData["qBt-firstLastPiecePriority"] = hasFirstLastPiecePriority(); + resumeData["qBt-sequential"] = isSequentialDownload(); + + resumeData["qBt-addedTime"] = addedTime().toSecsSinceEpoch(); + } + else { + const auto savePath = resumeData.find_key("save_path")->string(); + resumeData["save_path"] = Profile::instance()->toPortablePath(QString::fromStdString(savePath)).toStdString(); + } + resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance()->toPortablePath(m_savePath).toStdString(); + resumeData["qBt-ratioLimit"] = static_cast(m_ratioLimit * 1000); + resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit; + resumeData["qBt-category"] = m_category.toStdString(); + resumeData["qBt-tags"] = setToEntryList(m_tags); + resumeData["qBt-name"] = m_name.toStdString(); + resumeData["qBt-seedStatus"] = m_hasSeedStatus; + resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled; + resumeData["qBt-queuePosition"] = (static_cast(nativeHandle().queue_position()) + 1); // qBt starts queue at 1 + resumeData["qBt-hasRootFolder"] = m_hasRootFolder; + +#if (LIBTORRENT_VERSION_NUM < 10200) + if (m_nativeStatus.stop_when_ready) { +#else + if (m_nativeStatus.flags & lt::torrent_flags::stop_when_ready) { +#endif + // We need to redefine these values when torrent starting/rechecking + // in "paused" state since native values can be logically wrong + // (torrent can be not paused and auto_managed when it is checking). + resumeData["paused"] = true; + resumeData["auto_managed"] = false; + } + + m_session->handleTorrentResumeDataReady(this, resumeDataPtr); +} + +void TorrentHandleImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p) +{ + // if torrent has no metadata we should save dummy fastresume data + // containing Magnet URI and qBittorrent own resume data only + if (p->error.value() == lt::errors::no_metadata) { + handleSaveResumeDataAlert(nullptr); + } + else { + LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"") + .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL); + m_session->handleTorrentResumeDataFailed(this); + } +} + +void TorrentHandleImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p) +{ + m_fastresumeDataRejected = true; + + if (p->error.value() == lt::errors::mismatching_file_size) { + // Mismatching file size (files were probably moved) + m_hasMissingFiles = true; + LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL); + } + else { + LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...") + .arg(name(), QString::fromStdString(p->message())), Log::WARNING); + } +} + +void TorrentHandleImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p) +{ + // We don't really need to call updateStatus() in this place. + // All we need to do is make sure we have a valid instance of the TorrentInfo object. + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + + // remove empty leftover folders + // for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will + // be removed if they are empty + const QString oldFilePath = m_oldPath[p->index].takeFirst(); + const QString newFilePath = Utils::Fs::toUniformPath(p->new_name()); + + if (m_oldPath[p->index].isEmpty()) + m_oldPath.remove(p->index); + + QVector oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts); + oldPathParts.removeLast(); // drop file name part + QVector newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts); + newPathParts.removeLast(); // drop file name part + +#if defined(Q_OS_WIN) + const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; +#else + const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; +#endif + + int pathIdx = 0; + while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) { + if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0) + break; + ++pathIdx; + } + + for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) { + QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/"))); + oldPathParts.removeLast(); + } + + --m_renameCount; + while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) + m_moveFinishedTriggers.takeFirst()(); + + if (isPaused() && (m_renameCount == 0)) + saveResumeData(); // otherwise the new path will not be saved +} + +void TorrentHandleImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p) +{ + LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"") + .arg(name(), filePath(LTUnderlyingType {p->index}) + , QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING); + + m_oldPath[p->index].removeFirst(); + if (m_oldPath[p->index].isEmpty()) + m_oldPath.remove(p->index); + + --m_renameCount; + while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) + m_moveFinishedTriggers.takeFirst()(); + + if (isPaused() && (m_renameCount == 0)) + saveResumeData(); // otherwise the new path will not be saved +} + +void TorrentHandleImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) +{ + // We don't really need to call updateStatus() in this place. + // All we need to do is make sure we have a valid instance of the TorrentInfo object. + m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + + qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name())); + if (m_session->isAppendExtensionEnabled()) { + QString name = filePath(LTUnderlyingType {p->index}); + if (name.endsWith(QB_EXT)) { + const QString oldName = name; + name.chop(QB_EXT.size()); + qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name)); + renameFile(LTUnderlyingType {p->index}, name); + } + } +} + +void TorrentHandleImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) +{ + Q_UNUSED(p); + qDebug("Metadata received for torrent %s.", qUtf8Printable(name())); + updateStatus(); + if (m_session->isAppendExtensionEnabled()) + manageIncompleteFiles(); + if (!m_hasRootFolder) + m_torrentInfo.stripRootFolder(); + if (filesCount() == 1) + m_hasRootFolder = false; + m_session->handleTorrentMetadataReceived(this); + + if (isPaused()) { + // XXX: Unfortunately libtorrent-rasterbar does not send a torrent_paused_alert + // and the torrent can be paused when metadata is received + m_speedMonitor.reset(); + m_session->handleTorrentPaused(this); + } + + // If first/last piece priority was specified when adding this torrent, we can set it + // now that we have metadata: + if (m_needsToSetFirstLastPiecePriority) { + setFirstLastPiecePriority(true); + m_needsToSetFirstLastPiecePriority = false; + } +} + +void TorrentHandleImpl::handlePerformanceAlert(const lt::performance_alert *p) const +{ + LogMsg((tr("Performance alert: ") + QString::fromStdString(p->message())) + , Log::INFO); +} + +void TorrentHandleImpl::handleTempPathChanged() +{ + adjustActualSavePath(); +} + +void TorrentHandleImpl::handleCategorySavePathChanged() +{ + if (m_useAutoTMM) + move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); +} + +void TorrentHandleImpl::handleAppendExtensionToggled() +{ + if (!hasMetadata()) return; + + manageIncompleteFiles(); +} + +void TorrentHandleImpl::handleAlert(const lt::alert *a) +{ + switch (a->type()) { + case lt::file_renamed_alert::alert_type: + handleFileRenamedAlert(static_cast(a)); + break; + case lt::file_rename_failed_alert::alert_type: + handleFileRenameFailedAlert(static_cast(a)); + break; + case lt::file_completed_alert::alert_type: + handleFileCompletedAlert(static_cast(a)); + break; + case lt::torrent_finished_alert::alert_type: + handleTorrentFinishedAlert(static_cast(a)); + break; + case lt::save_resume_data_alert::alert_type: + handleSaveResumeDataAlert(static_cast(a)); + break; + case lt::save_resume_data_failed_alert::alert_type: + handleSaveResumeDataFailedAlert(static_cast(a)); + break; + case lt::torrent_paused_alert::alert_type: + handleTorrentPausedAlert(static_cast(a)); + break; + case lt::torrent_resumed_alert::alert_type: + handleTorrentResumedAlert(static_cast(a)); + break; + case lt::tracker_error_alert::alert_type: + handleTrackerErrorAlert(static_cast(a)); + break; + case lt::tracker_reply_alert::alert_type: + handleTrackerReplyAlert(static_cast(a)); + break; + case lt::tracker_warning_alert::alert_type: + handleTrackerWarningAlert(static_cast(a)); + break; + case lt::metadata_received_alert::alert_type: + handleMetadataReceivedAlert(static_cast(a)); + break; + case lt::fastresume_rejected_alert::alert_type: + handleFastResumeRejectedAlert(static_cast(a)); + break; + case lt::torrent_checked_alert::alert_type: + handleTorrentCheckedAlert(static_cast(a)); + break; + case lt::performance_alert::alert_type: + handlePerformanceAlert(static_cast(a)); + break; + } +} + +void TorrentHandleImpl::manageIncompleteFiles() +{ + const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled(); + const QVector fp = filesProgress(); + if (fp.size() != filesCount()) { + qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress"; + return; + } + + for (int i = 0; i < filesCount(); ++i) { + QString name = filePath(i); + if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) { + if (!name.endsWith(QB_EXT)) { + const QString newName = name + QB_EXT; + qDebug() << "Renaming" << name << "to" << newName; + renameFile(i, newName); + } + } + else { + if (name.endsWith(QB_EXT)) { + const QString oldName = name; + name.chop(QB_EXT.size()); + qDebug() << "Renaming" << oldName << "to" << name; + renameFile(i, name); + } + } + } +} + +void TorrentHandleImpl::adjustActualSavePath() +{ + if (!isMoveInProgress()) + adjustActualSavePath_impl(); + else + m_moveFinishedTriggers.append([this]() { adjustActualSavePath_impl(); }); +} + +void TorrentHandleImpl::adjustActualSavePath_impl() +{ + const bool needUseTempDir = useTempPath(); + const QDir tempDir {m_session->torrentTempPath(info())}; + const QDir currentDir {actualStorageLocation()}; + const QDir targetDir {needUseTempDir ? tempDir : QDir {savePath()}}; + + if (targetDir == currentDir) return; + + if (!needUseTempDir) { + if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()})) { + // torrent without root folder still has it in its temporary save path + // so its temp path isn't equal to temp path root + const QString currentDirPath = currentDir.absolutePath(); + m_moveFinishedTriggers.append([currentDirPath] + { + qDebug() << "Removing torrent temp folder:" << currentDirPath; + Utils::Fs::smartRemoveEmptyFolderTree(currentDirPath); + }); + } + } + + moveStorage(Utils::Fs::toNativePath(targetDir.absolutePath()), MoveStorageMode::Overwrite); +} + +lt::torrent_handle TorrentHandleImpl::nativeHandle() const +{ + return m_nativeHandle; +} + +void TorrentHandleImpl::updateTorrentInfo() +{ + if (!hasMetadata()) return; + + m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file.lock()); +} + +bool TorrentHandleImpl::isMoveInProgress() const +{ + return m_storageIsMoving; +} + +bool TorrentHandleImpl::useTempPath() const +{ + return !m_tempPathDisabled && m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus); +} + +void TorrentHandleImpl::updateStatus() +{ + updateStatus(m_nativeHandle.status()); +} + +void TorrentHandleImpl::updateStatus(const lt::torrent_status &nativeStatus) +{ + m_nativeStatus = nativeStatus; + + updateState(); + updateTorrentInfo(); + + // NOTE: Don't change the order of these conditionals! + // Otherwise it will not work properly since torrent can be CheckingDownloading. + if (isChecking()) + m_unchecked = false; + else if (isDownloading()) + m_unchecked = true; + + m_speedMonitor.addSample({nativeStatus.download_payload_rate + , nativeStatus.upload_payload_rate}); +} + +void TorrentHandleImpl::setRatioLimit(qreal limit) +{ + if (limit < USE_GLOBAL_RATIO) + limit = NO_RATIO_LIMIT; + else if (limit > MAX_RATIO) + limit = MAX_RATIO; + + if (m_ratioLimit != limit) { + m_ratioLimit = limit; + m_session->handleTorrentShareLimitChanged(this); + } +} + +void TorrentHandleImpl::setSeedingTimeLimit(int limit) +{ + if (limit < USE_GLOBAL_SEEDING_TIME) + limit = NO_SEEDING_TIME_LIMIT; + else if (limit > MAX_SEEDING_TIME) + limit = MAX_SEEDING_TIME; + + if (m_seedingTimeLimit != limit) { + m_seedingTimeLimit = limit; + m_session->handleTorrentShareLimitChanged(this); + } +} + +void TorrentHandleImpl::setUploadLimit(const int limit) +{ + m_nativeHandle.set_upload_limit(limit); +} + +void TorrentHandleImpl::setDownloadLimit(const int limit) +{ + m_nativeHandle.set_download_limit(limit); +} + +void TorrentHandleImpl::setSuperSeeding(const bool enable) +{ +#if (LIBTORRENT_VERSION_NUM < 10200) + m_nativeHandle.super_seeding(enable); +#else + if (enable) + m_nativeHandle.set_flags(lt::torrent_flags::super_seeding); + else + m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding); +#endif +} + +void TorrentHandleImpl::flushCache() const +{ + m_nativeHandle.flush_cache(); +} + +QString TorrentHandleImpl::createMagnetURI() const +{ + return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle)); +} + +void TorrentHandleImpl::prioritizeFiles(const QVector &priorities) +{ + if (!hasMetadata()) return; + if (priorities.size() != filesCount()) return; + + // Save first/last piece first option state + const bool firstLastPieceFirst = hasFirstLastPiecePriority(); + + // Reset 'm_hasSeedStatus' if needed in order to react again to + // 'torrent_finished_alert' and eg show tray notifications + const QVector progress = filesProgress(); + const QVector oldPriorities = filePriorities(); + for (int i = 0; i < oldPriorities.size(); ++i) { + if ((oldPriorities[i] == DownloadPriority::Ignored) + && (priorities[i] > DownloadPriority::Ignored) + && (progress[i] < 1.0)) { + m_hasSeedStatus = false; + break; + } + } + + qDebug() << Q_FUNC_INFO << "Changing files priorities..."; + m_nativeHandle.prioritize_files(toLTDownloadPriorities(priorities)); + + qDebug() << Q_FUNC_INFO << "Moving unwanted files to .unwanted folder and conversely..."; + const QString spath = savePath(true); + for (int i = 0; i < priorities.size(); ++i) { + const QString filepath = filePath(i); + // Move unwanted files to a .unwanted subfolder + if (priorities[i] == DownloadPriority::Ignored) { + const QString oldAbsPath = QDir(spath).absoluteFilePath(filepath); + const QString parentAbsPath = Utils::Fs::branchPath(oldAbsPath); + // Make sure the file does not already exists + if (QDir(parentAbsPath).dirName() != ".unwanted") { + const QString unwantedAbsPath = parentAbsPath + "/.unwanted"; + const QString newAbsPath = unwantedAbsPath + '/' + Utils::Fs::fileName(filepath); + qDebug() << "Unwanted path is" << unwantedAbsPath; + if (QFile::exists(newAbsPath)) { + qWarning() << "File" << newAbsPath << "already exists at destination."; + continue; + } + + const bool created = QDir().mkpath(unwantedAbsPath); + qDebug() << "unwanted folder was created:" << created; +#ifdef Q_OS_WIN + if (created) { + // Hide the folder on Windows + qDebug() << "Hiding folder (Windows)"; + std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString(); + DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str()); + bool ret = ::SetFileAttributesW(winPath.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN); + Q_ASSERT(ret != 0); Q_UNUSED(ret); + } +#endif + QString parentPath = Utils::Fs::branchPath(filepath); + if (!parentPath.isEmpty() && !parentPath.endsWith('/')) + parentPath += '/'; + renameFile(i, parentPath + ".unwanted/" + Utils::Fs::fileName(filepath)); + } + } + + // Move wanted files back to their original folder + if (priorities[i] > DownloadPriority::Ignored) { + const QString parentRelPath = Utils::Fs::branchPath(filepath); + if (QDir(parentRelPath).dirName() == ".unwanted") { + const QString oldName = Utils::Fs::fileName(filepath); + const QString newRelPath = Utils::Fs::branchPath(parentRelPath); + if (newRelPath.isEmpty()) + renameFile(i, oldName); + else + renameFile(i, QDir(newRelPath).filePath(oldName)); + + // Remove .unwanted directory if empty + qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted"); + QDir(spath + '/' + newRelPath).rmdir(".unwanted"); + } + } + } + + // Restore first/last piece first option if necessary + if (firstLastPieceFirst) + setFirstLastPiecePriorityImpl(true, priorities); +} + +QVector TorrentHandleImpl::availableFileFractions() const +{ + const int filesCount = this->filesCount(); + if (filesCount < 0) return {}; + + const QVector piecesAvailability = pieceAvailability(); + // libtorrent returns empty array for seeding only torrents + if (piecesAvailability.empty()) return QVector(filesCount, -1.); + + QVector res; + res.reserve(filesCount); + const TorrentInfo info = this->info(); + for (int i = 0; i < filesCount; ++i) { + const TorrentInfo::PieceRange filePieces = info.filePieces(i); + + int availablePieces = 0; + for (int piece = filePieces.first(); piece <= filePieces.last(); ++piece) { + availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0; + } + res.push_back(static_cast(availablePieces) / filePieces.size()); + } + return res; +} diff --git a/src/base/bittorrent/torrenthandleimpl.h b/src/base/bittorrent/torrenthandleimpl.h new file mode 100644 index 000000000..9cc139933 --- /dev/null +++ b/src/base/bittorrent/torrenthandleimpl.h @@ -0,0 +1,337 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "private/speedmonitor.h" +#include "infohash.h" +#include "torrenthandle.h" +#include "torrentinfo.h" + +namespace BitTorrent +{ + class Session; + struct AddTorrentParams; + + struct CreateTorrentParams + { + bool restored = false; // is existing torrent job? + // for both new and restored torrents + QString name; + QString category; + QSet tags; + QString savePath; + bool disableTempPath = false; + bool sequential = false; + bool firstLastPiecePriority = false; + bool hasSeedStatus = false; + bool skipChecking = false; + bool hasRootFolder = true; + bool forced = false; + bool paused = false; + int uploadLimit = -1; + int downloadLimit = -1; + // for new torrents + QVector filePriorities; + QDateTime addedTime; + // for restored torrents + qreal ratioLimit = TorrentHandle::USE_GLOBAL_RATIO; + int seedingTimeLimit = TorrentHandle::USE_GLOBAL_SEEDING_TIME; + + CreateTorrentParams() = default; + explicit CreateTorrentParams(const AddTorrentParams ¶ms); + }; + + enum class MoveStorageMode + { + KeepExistingFiles, + Overwrite + }; + + class TorrentHandleImpl final : public QObject, public TorrentHandle + { + Q_DISABLE_COPY(TorrentHandleImpl) + Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandleImpl) + + public: + TorrentHandleImpl(Session *session, const lt::torrent_handle &nativeHandle, + const CreateTorrentParams ¶ms); + ~TorrentHandleImpl() override; + + bool isValid() const; + + InfoHash hash() const override; + QString name() const override; + QDateTime creationDate() const override; + QString creator() const override; + QString comment() const override; + bool isPrivate() const override; + qlonglong totalSize() const override; + qlonglong wantedSize() const override; + qlonglong completedSize() const override; + qlonglong incompletedSize() const override; + qlonglong pieceLength() const override; + qlonglong wastedSize() const override; + QString currentTracker() const override; + + QString savePath(bool actual = false) const override; + QString rootPath(bool actual = false) const override; + QString contentPath(bool actual = false) const override; + + bool useTempPath() const override; + + bool isAutoTMMEnabled() const override; + void setAutoTMMEnabled(bool enabled) override; + QString category() const override; + bool belongsToCategory(const QString &category) const override; + bool setCategory(const QString &category) override; + + QSet tags() const override; + bool hasTag(const QString &tag) const override; + bool addTag(const QString &tag) override; + bool removeTag(const QString &tag) override; + void removeAllTags() override; + + bool hasRootFolder() const override; + + int filesCount() const override; + int piecesCount() const override; + int piecesHave() const override; + qreal progress() const override; + QDateTime addedTime() const override; + qreal ratioLimit() const override; + int seedingTimeLimit() const override; + + QString filePath(int index) const override; + QString fileName(int index) const override; + qlonglong fileSize(int index) const override; + QStringList absoluteFilePaths() const override; + QStringList absoluteFilePathsUnwanted() const override; + QVector filePriorities() const override; + + TorrentInfo info() const override; + bool isSeed() const override; + bool isPaused() const override; + bool isResumed() const override; + bool isQueued() const override; + bool isForced() const override; + bool isChecking() const override; + bool isDownloading() const override; + bool isUploading() const override; + bool isCompleted() const override; + bool isActive() const override; + bool isInactive() const override; + bool isErrored() const override; + bool isSequentialDownload() const override; + bool hasFirstLastPiecePriority() const override; + TorrentState state() const override; + bool hasMetadata() const override; + bool hasMissingFiles() const override; + bool hasError() const override; + bool hasFilteredPieces() const override; + int queuePosition() const override; + QVector trackers() const override; + QHash trackerInfos() const override; + QVector urlSeeds() const override; + QString error() const override; + qlonglong totalDownload() const override; + qlonglong totalUpload() const override; + qlonglong activeTime() const override; + qlonglong finishedTime() const override; + qlonglong seedingTime() const override; + qlonglong eta() const override; + QVector filesProgress() const override; + int seedsCount() const override; + int peersCount() const override; + int leechsCount() const override; + int totalSeedsCount() const override; + int totalPeersCount() const override; + int totalLeechersCount() const override; + int completeCount() const override; + int incompleteCount() const override; + QDateTime lastSeenComplete() const override; + QDateTime completedTime() const override; + qlonglong timeSinceUpload() const override; + qlonglong timeSinceDownload() const override; + qlonglong timeSinceActivity() const override; + int downloadLimit() const override; + int uploadLimit() const override; + bool superSeeding() const override; + QVector peers() const override; + QBitArray pieces() const override; + QBitArray downloadingPieces() const override; + QVector pieceAvailability() const override; + qreal distributedCopies() const override; + qreal maxRatio() const override; + int maxSeedingTime() const override; + qreal realRatio() const override; + int uploadPayloadRate() const override; + int downloadPayloadRate() const override; + qlonglong totalPayloadUpload() const override; + qlonglong totalPayloadDownload() const override; + int connectionsCount() const override; + int connectionsLimit() const override; + qlonglong nextAnnounce() const override; + QVector availableFileFractions() const override; + + void setName(const QString &name) override; + void setSequentialDownload(bool enable) override; + void setFirstLastPiecePriority(bool enabled) override; + void pause() override; + void resume(bool forced = false) override; + void move(QString path) override; + void forceReannounce(int index = -1) override; + void forceDHTAnnounce() override; + void forceRecheck() override; + void renameFile(int index, const QString &name) override; + void prioritizeFiles(const QVector &priorities) override; + void setRatioLimit(qreal limit) override; + void setSeedingTimeLimit(int limit) override; + void setUploadLimit(int limit) override; + void setDownloadLimit(int limit) override; + void setSuperSeeding(bool enable) override; + void flushCache() const override; + void addTrackers(const QVector &trackers) override; + void replaceTrackers(const QVector &trackers) override; + void addUrlSeeds(const QVector &urlSeeds) override; + void removeUrlSeeds(const QVector &urlSeeds) override; + bool connectPeer(const PeerAddress &peerAddress) override; + + QString createMagnetURI() const override; + + bool needSaveResumeData() const; + + // Session interface + lt::torrent_handle nativeHandle() const; + + void handleAlert(const lt::alert *a); + void handleStateUpdate(const lt::torrent_status &nativeStatus); + void handleTempPathChanged(); + void handleCategorySavePathChanged(); + void handleAppendExtensionToggled(); + void saveResumeData(); + void handleStorageMoved(const QString &newPath, const QString &errorMessage); + + private: + typedef std::function EventTrigger; + +#if (LIBTORRENT_VERSION_NUM < 10200) + using LTFileIndex = int; +#else + using LTFileIndex = lt::file_index_t; +#endif + + void updateStatus(); + void updateStatus(const lt::torrent_status &nativeStatus); + void updateState(); + void updateTorrentInfo(); + + void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p); + void handleFileCompletedAlert(const lt::file_completed_alert *p); + void handleFileRenamedAlert(const lt::file_renamed_alert *p); + void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p); + void handleMetadataReceivedAlert(const lt::metadata_received_alert *p); + void handlePerformanceAlert(const lt::performance_alert *p) const; + void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p); + void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p); + void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p); + void handleTorrentFinishedAlert(const lt::torrent_finished_alert *p); + void handleTorrentPausedAlert(const lt::torrent_paused_alert *p); + void handleTorrentResumedAlert(const lt::torrent_resumed_alert *p); + void handleTrackerErrorAlert(const lt::tracker_error_alert *p); + void handleTrackerReplyAlert(const lt::tracker_reply_alert *p); + void handleTrackerWarningAlert(const lt::tracker_warning_alert *p); + + void resume_impl(bool forced); + bool isMoveInProgress() const; + QString actualStorageLocation() const; + bool isAutoManaged() const; + void setAutoManaged(bool enable); + + void adjustActualSavePath(); + void adjustActualSavePath_impl(); + void move_impl(QString path, MoveStorageMode mode); + void moveStorage(const QString &newPath, MoveStorageMode mode); + void manageIncompleteFiles(); + void setFirstLastPiecePriorityImpl(bool enabled, const QVector &updatedFilePrio = {}); + + Session *const m_session; + lt::torrent_handle m_nativeHandle; + lt::torrent_status m_nativeStatus; + TorrentState m_state; + TorrentInfo m_torrentInfo; + SpeedMonitor m_speedMonitor; + + InfoHash m_hash; + + bool m_storageIsMoving = false; + // m_moveFinishedTriggers is activated only when the following conditions are met: + // all file rename jobs complete, all file move jobs complete + QQueue m_moveFinishedTriggers; + int m_renameCount; + + // Until libtorrent provide an "old_name" field in `file_renamed_alert` + // we will rely on this workaround to remove empty leftover folders + QHash> m_oldPath; + + bool m_useAutoTMM; + + // Persistent data + QString m_name; + QString m_savePath; + QString m_category; + QSet m_tags; + bool m_hasSeedStatus; + qreal m_ratioLimit; + int m_seedingTimeLimit; + bool m_tempPathDisabled; + bool m_fastresumeDataRejected; + bool m_hasMissingFiles; + bool m_hasRootFolder; + bool m_needsToSetFirstLastPiecePriority; + + QHash m_trackerInfos; + + bool m_unchecked = false; + }; +} diff --git a/src/base/torrentfilter.cpp b/src/base/torrentfilter.cpp index e8fe74498..43d5a0ea7 100644 --- a/src/base/torrentfilter.cpp +++ b/src/base/torrentfilter.cpp @@ -28,6 +28,7 @@ #include "torrentfilter.h" +#include "bittorrent/infohash.h" #include "bittorrent/torrenthandle.h" const QString TorrentFilter::AnyCategory; diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index 9f2314e53..cfec9b96b 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -28,6 +28,7 @@ #include "fs.h" +#include #include #if defined(Q_OS_WIN) diff --git a/src/gui/properties/piecesbar.cpp b/src/gui/properties/piecesbar.cpp index 894cb3fe0..8ac138b67 100644 --- a/src/gui/properties/piecesbar.cpp +++ b/src/gui/properties/piecesbar.cpp @@ -37,7 +37,9 @@ #include #include +#include "base/indexrange.h" #include "base/bittorrent/torrenthandle.h" +#include "base/bittorrent/torrentinfo.h" #include "base/utils/misc.h" namespace diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 8698c6e7e..e4e89680f 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -43,6 +43,7 @@ #include #include "base/bittorrent/downloadpriority.h" +#include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/preferences.h" diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 0edfd7637..cf7ccf8fa 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -38,6 +38,7 @@ #include #include +#include "base/bittorrent/infohash.h" #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" #include "base/bittorrent/trackerentry.h" @@ -223,7 +224,7 @@ void StatusFilterWidget::updateTorrentNumbers() int nbStalledDownloading = 0; int nbErrored = 0; - const QHash torrents = BitTorrent::Session::instance()->torrents(); + const QVector torrents = BitTorrent::Session::instance()->torrents(); for (const BitTorrent::TorrentHandle *torrent : torrents) { if (torrent->isDownloading()) ++nbDownloading; diff --git a/src/gui/transferlistsortmodel.cpp b/src/gui/transferlistsortmodel.cpp index f704b0515..1bc1e70bd 100644 --- a/src/gui/transferlistsortmodel.cpp +++ b/src/gui/transferlistsortmodel.cpp @@ -31,6 +31,7 @@ #include #include +#include "base/bittorrent/infohash.h" #include "base/bittorrent/torrenthandle.h" #include "base/global.h" #include "base/types.h" diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 17c2e92ff..b4006b399 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -652,7 +652,7 @@ void TransferListWidget::copySelectedMagnetURIs() const { QStringList magnetUris; for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) - magnetUris << torrent->toMagnetUri(); + magnetUris << torrent->createMagnetURI(); qApp->clipboard()->setText(magnetUris.join('\n')); } diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index fd77c8132..23e53c6ee 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -30,6 +30,7 @@ #include +#include "base/bittorrent/infohash.h" #include "base/bittorrent/torrenthandle.h" #include "base/utils/fs.h" @@ -85,7 +86,7 @@ QVariantMap serialize(const BitTorrent::TorrentHandle &torrent) QVariantMap ret = { {KEY_TORRENT_HASH, QString(torrent.hash())}, {KEY_TORRENT_NAME, torrent.name()}, - {KEY_TORRENT_MAGNET_URI, torrent.toMagnetUri()}, + {KEY_TORRENT_MAGNET_URI, torrent.createMagnetURI()}, {KEY_TORRENT_SIZE, torrent.wantedSize()}, {KEY_TORRENT_PROGRESS, torrent.progress()}, {KEY_TORRENT_DLSPEED, torrent.downloadPayloadRate()}, diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index 17df7ca0c..8c5fe4621 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -34,6 +34,7 @@ #include #include +#include "base/bittorrent/infohash.h" #include "base/bittorrent/peeraddress.h" #include "base/bittorrent/peerinfo.h" #include "base/bittorrent/session.h"