diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 80d9839b8..17df907d6 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -55,6 +55,7 @@ namespace BitTorrent bool firstLastPiecePriority = false; bool addForced = false; std::optional addPaused; + std::optional stopCondition; PathList filePaths; // used if TorrentInfo is set QVector filePriorities; // used if TorrentInfo is set bool skipChecking = false; diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index b541fbf0e..f500c5e2d 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -245,6 +245,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre // fromLTString(root.dict_find_string_value("qBt-contentLayout")), TorrentContentLayout::Default); // === END REPLACEMENT CODE === // + torrentParams.stopCondition = Utils::String::toEnum( + fromLTString(resumeDataRoot.dict_find_string_value("qBt-stopCondition")), Torrent::StopCondition::None); + const lt::string_view ratioLimitString = resumeDataRoot.dict_find_string_value("qBt-ratioLimit"); if (ratioLimitString.empty()) torrentParams.ratioLimit = resumeDataRoot.dict_find_int_value("qBt-ratioLimit", Torrent::USE_GLOBAL_RATIO * 1000) / 1000.0; @@ -284,20 +287,14 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre p.save_path = Profile::instance()->fromPortablePath( Path(fromLTString(p.save_path))).toString().toStdString(); + torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed); + torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed) + ? TorrentOperatingMode::AutoManaged : TorrentOperatingMode::Forced; + if (p.flags & lt::torrent_flags::stop_when_ready) { - // If torrent has "stop_when_ready" flag set then it is actually "stopped" - torrentParams.stopped = true; - torrentParams.operatingMode = TorrentOperatingMode::AutoManaged; - // ...but temporarily "resumed" to perform some service jobs (e.g. checking) - p.flags &= ~lt::torrent_flags::paused; - p.flags |= lt::torrent_flags::auto_managed; - } - else - { - torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed); - torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed) - ? TorrentOperatingMode::AutoManaged : TorrentOperatingMode::Forced; + p.flags &= ~lt::torrent_flags::stop_when_ready; + torrentParams.stopCondition = Torrent::StopCondition::FilesChecked; } const bool hasMetadata = (p.ti && p.ti->is_valid()); @@ -393,6 +390,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co data["qBt-seedStatus"] = resumeData.hasSeedStatus; data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString(); data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority; + data["qBt-stopCondition"] = Utils::String::fromEnum(resumeData.stopCondition).toStdString(); if (!resumeData.useAutoTMM) { diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 3719d4356..a969d5a3d 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -61,7 +61,7 @@ namespace { const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_qs; - const int DB_VERSION = 2; + const int DB_VERSION = 3; const QString DB_TABLE_META = u"meta"_qs; const QString DB_TABLE_TORRENTS = u"torrents"_qs; @@ -94,6 +94,7 @@ namespace const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status"); const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode"); const Column DB_COLUMN_STOPPED = makeColumn("stopped"); + const Column DB_COLUMN_STOP_CONDITION = makeColumn("stop_condition"); const Column DB_COLUMN_RESUMEDATA = makeColumn("libtorrent_resume_data"); const Column DB_COLUMN_METADATA = makeColumn("metadata"); const Column DB_COLUMN_VALUE = makeColumn("value"); @@ -213,6 +214,8 @@ namespace BitTorrent resumeData.operatingMode = Utils::String::toEnum( query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged); resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool(); + resumeData.stopCondition = Utils::String::toEnum( + query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None); resumeData.savePath = Profile::instance()->fromPortablePath( Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString())); @@ -241,6 +244,12 @@ namespace BitTorrent p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path))) .toString().toStdString(); + if (p.flags & lt::torrent_flags::stop_when_ready) + { + p.flags &= ~lt::torrent_flags::stop_when_ready; + resumeData.stopCondition = Torrent::StopCondition::FilesChecked; + } + return resumeData; } } @@ -263,9 +272,9 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject } else { - const int dbVersion = currentDBVersion(); - if ((dbVersion == 1) || !db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name)) - updateDBFromVersion1(); + const int dbVersion = (!db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name) ? 1 : currentDBVersion()); + if (dbVersion != DB_VERSION) + updateDB(dbVersion); } m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock); @@ -507,8 +516,11 @@ void BitTorrent::DBResumeDataStorage::createDB() const } } -void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const +void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const { + Q_ASSERT(fromVersion > 0); + Q_ASSERT(fromVersion != DB_VERSION); + auto db = QSqlDatabase::database(DB_CONNECTION_NAME); const QWriteLocker locker {&m_dbLock}; @@ -520,10 +532,21 @@ void BitTorrent::DBResumeDataStorage::updateDBFromVersion1() const try { - const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs - .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT")); - if (!query.exec(alterTableTorrentsQuery)) - throw RuntimeError(query.lastError().text()); + if (fromVersion == 1) + { + const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs + .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT")); + if (!query.exec(alterTableTorrentsQuery)) + throw RuntimeError(query.lastError().text()); + } + + if (fromVersion <= 2) + { + const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs + .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`")); + if (!query.exec(alterTableTorrentsQuery)) + throw RuntimeError(query.lastError().text()); + } const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE}); if (!query.prepare(updateMetaVersionQuery)) @@ -662,6 +685,7 @@ void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const L query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasSeedStatus); query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode)); query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped); + query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(resumeData.stopCondition)); if (!resumeData.useAutoTMM) { diff --git a/src/base/bittorrent/dbresumedatastorage.h b/src/base/bittorrent/dbresumedatastorage.h index 1657e584f..1dd9392cb 100644 --- a/src/base/bittorrent/dbresumedatastorage.h +++ b/src/base/bittorrent/dbresumedatastorage.h @@ -57,7 +57,7 @@ namespace BitTorrent void doLoadAll() const override; int currentDBVersion() const; void createDB() const; - void updateDBFromVersion1() const; + void updateDB(int fromVersion) const; QThread *m_ioThread = nullptr; diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index 4892bece8..f49870c89 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -54,6 +54,7 @@ namespace BitTorrent bool firstLastPiecePriority = false; bool hasSeedStatus = false; bool stopped = false; + Torrent::StopCondition stopCondition; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; diff --git a/src/base/bittorrent/nativetorrentextension.cpp b/src/base/bittorrent/nativetorrentextension.cpp index b0ea54a21..1f8ab1a1e 100644 --- a/src/base/bittorrent/nativetorrentextension.cpp +++ b/src/base/bittorrent/nativetorrentextension.cpp @@ -30,14 +30,6 @@ #include -namespace -{ - bool isAutoManaged(const lt::torrent_status &torrentStatus) - { - return static_cast(torrentStatus.flags & lt::torrent_flags::auto_managed); - } -} - NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrentHandle, ExtensionData *data) : m_torrentHandle {torrentHandle} , m_data {data} @@ -65,19 +57,10 @@ NativeTorrentExtension::~NativeTorrentExtension() delete m_data; } -bool NativeTorrentExtension::on_pause() -{ - if (!isAutoManaged(m_torrentHandle.status({}))) - m_torrentHandle.unset_flags(lt::torrent_flags::stop_when_ready); - - // return `false` to allow standard handler - // and other extensions to be also invoked. - return false; -} - void NativeTorrentExtension::on_state(const lt::torrent_status::state_t state) { - if (m_state == lt::torrent_status::downloading_metadata) + if ((m_state == lt::torrent_status::downloading_metadata) + || (m_state == lt::torrent_status::checking_files)) { m_torrentHandle.unset_flags(lt::torrent_flags::auto_managed); m_torrentHandle.pause(); diff --git a/src/base/bittorrent/nativetorrentextension.h b/src/base/bittorrent/nativetorrentextension.h index 8667eec50..4aed9e10d 100644 --- a/src/base/bittorrent/nativetorrentextension.h +++ b/src/base/bittorrent/nativetorrentextension.h @@ -40,7 +40,6 @@ public: ~NativeTorrentExtension(); private: - bool on_pause() override; void on_state(lt::torrent_status::state_t state) override; lt::torrent_handle m_torrentHandle; diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 2f62a7646..c0dd788fd 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -208,6 +208,8 @@ namespace BitTorrent virtual void setPeXEnabled(bool enabled) = 0; virtual bool isAddTorrentPaused() const = 0; virtual void setAddTorrentPaused(bool value) = 0; + virtual Torrent::StopCondition torrentStopCondition() const = 0; + virtual void setTorrentStopCondition(Torrent::StopCondition stopCondition) = 0; virtual TorrentContentLayout torrentContentLayout() const = 0; virtual void setTorrentContentLayout(TorrentContentLayout value) = 0; virtual bool isTrackerEnabled() const = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index c96b54625..ceca6ed64 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -438,6 +438,7 @@ SessionImpl::SessionImpl(QObject *parent) , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_qs), -1, [](qreal r) { return r < 0 ? -1. : r;}) , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_qs), -1, lowerLimited(-1)) , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_qs), false) + , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_qs), Torrent::StopCondition::None) , m_torrentContentLayout(BITTORRENT_SESSION_KEY(u"TorrentContentLayout"_qs), TorrentContentLayout::Original) , m_isAppendExtensionEnabled(BITTORRENT_SESSION_KEY(u"AddExtensionToIncompleteFiles"_qs), false) , m_refreshInterval(BITTORRENT_SESSION_KEY(u"RefreshInterval"_qs), 1500) @@ -950,6 +951,16 @@ void SessionImpl::setAddTorrentPaused(const bool value) m_isAddTorrentPaused = value; } +Torrent::StopCondition SessionImpl::torrentStopCondition() const +{ + return m_torrentStopCondition; +} + +void SessionImpl::setTorrentStopCondition(const Torrent::StopCondition stopCondition) +{ + m_torrentStopCondition = stopCondition; +} + bool SessionImpl::isTrackerEnabled() const { return m_isTrackerEnabled; @@ -2497,6 +2508,7 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout()); loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged); loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused()); + loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition()); loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit; loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit; diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 1dabf4e3f..0d105a395 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -188,6 +188,8 @@ namespace BitTorrent void setPeXEnabled(bool enabled) override; bool isAddTorrentPaused() const override; void setAddTorrentPaused(bool value) override; + Torrent::StopCondition torrentStopCondition() const override; + void setTorrentStopCondition(Torrent::StopCondition stopCondition) override; TorrentContentLayout torrentContentLayout() const override; void setTorrentContentLayout(TorrentContentLayout value) override; bool isTrackerEnabled() const override; @@ -620,6 +622,7 @@ namespace BitTorrent CachedSettingValue m_globalMaxRatio; CachedSettingValue m_globalMaxSeedingMinutes; CachedSettingValue m_isAddTorrentPaused; + CachedSettingValue m_torrentStopCondition; CachedSettingValue m_torrentContentLayout; CachedSettingValue m_isAppendExtensionEnabled; CachedSettingValue m_refreshInterval; diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index e1872e884..36bc8e072 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -108,7 +108,17 @@ namespace BitTorrent class Torrent : public AbstractFileStorage { + Q_GADGET + public: + enum class StopCondition + { + None = 0, + MetadataReceived = 1, + FilesChecked = 2 + }; + Q_ENUM(StopCondition) + static const qreal USE_GLOBAL_RATIO; static const qreal NO_RATIO_LIMIT; @@ -300,6 +310,9 @@ namespace BitTorrent virtual void clearPeers() = 0; virtual bool setMetadata(const TorrentInfo &torrentInfo) = 0; + virtual StopCondition stopCondition() const = 0; + virtual void setStopCondition(StopCondition stopCondition) = 0; + virtual QString createMagnetURI() const = 0; virtual nonstd::expected exportToBuffer() const = 0; virtual nonstd::expected exportToFile(const Path &path) const = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 3a919d1f1..f6a87e90e 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -295,6 +295,8 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession } } + setStopCondition(params.stopCondition); + const auto *extensionData = static_cast(m_ltAddTorrentParams.userdata); m_trackerEntries.reserve(static_cast(extensionData->trackers.size())); for (const lt::announce_entry &announceEntry : extensionData->trackers) @@ -993,9 +995,7 @@ void TorrentImpl::updateState() else m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata; } - else if ((m_nativeStatus.state == lt::torrent_status::checking_files) - && (!isPaused() || (m_nativeStatus.flags & lt::torrent_flags::auto_managed) - || !(m_nativeStatus.flags & lt::torrent_flags::paused))) + else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused()) { // If the torrent is not just in the "checking" state, but is being actually checked m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading; @@ -1423,8 +1423,8 @@ void TorrentImpl::forceRecheck() if (isPaused()) { // When "force recheck" is applied on paused torrent, we temporarily resume it - // (really we just allow libtorrent to resume it by enabling auto management for it). - m_nativeHandle.set_flags(lt::torrent_flags::stop_when_ready | lt::torrent_flags::auto_managed); + resume(); + setStopCondition(StopCondition::FilesChecked); } } @@ -1581,6 +1581,17 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi p.save_path = savePath.toString().toStdString(); p.ti = metadata; + if (stopCondition() == StopCondition::MetadataReceived) + { + m_stopCondition = StopCondition::None; + + m_isStopped = true; + p.flags |= lt::torrent_flags::paused; + p.flags &= ~lt::torrent_flags::auto_managed; + + m_session->handleTorrentPaused(this); + } + reload(); // If first/last piece priority was specified when adding this torrent, @@ -1639,6 +1650,7 @@ void TorrentImpl::pause() { if (!m_isStopped) { + m_stopCondition = StopCondition::None; m_isStopped = true; m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentPaused(this); @@ -1674,10 +1686,6 @@ void TorrentImpl::resume(const TorrentOperatingMode mode) if (m_isStopped) { - // Torrent may have been temporarily resumed to perform checking files - // so we have to ensure it will not pause after checking is done. - m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready); - m_isStopped = false; m_session->handleTorrentNeedSaveResumeData(this); m_session->handleTorrentResumed(this); @@ -1756,13 +1764,13 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p) return; } + if (stopCondition() == StopCondition::FilesChecked) + pause(); + m_statusUpdatedTriggers.enqueue([this]() { qDebug("\"%s\" have just finished checking.", qUtf8Printable(name())); - if (m_nativeStatus.need_save_resume) - m_session->handleTorrentNeedSaveResumeData(this); - if (!m_hasMissingFiles) { if ((progress() < 1.0) && (wantedSize() > 0)) @@ -1772,8 +1780,20 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p) adjustStorageLocation(); manageIncompleteFiles(); + + if (!isPaused()) + { + // torrent is internally paused using NativeTorrentExtension after files checked + // so we need to resume it if there is no corresponding "stop condition" set + setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged); + if (m_operatingMode == TorrentOperatingMode::Forced) + m_nativeHandle.resume(); + } } + if (m_nativeStatus.need_save_resume) + m_session->handleTorrentNeedSaveResumeData(this); + m_session->handleTorrentChecked(this); }); } @@ -1909,6 +1929,7 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority; resumeData.hasSeedStatus = m_hasSeedStatus; resumeData.stopped = m_isStopped; + resumeData.stopCondition = m_stopCondition; resumeData.operatingMode = m_operatingMode; resumeData.ltAddTorrentParams = m_ltAddTorrentParams; resumeData.useAutoTMM = m_useAutoTMM; @@ -2166,6 +2187,28 @@ bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo) #endif } +Torrent::StopCondition TorrentImpl::stopCondition() const +{ + return m_stopCondition; +} + +void TorrentImpl::setStopCondition(const StopCondition stopCondition) +{ + if (stopCondition == m_stopCondition) + return; + + if (isPaused()) + return; + + if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata()) + return; + + if ((stopCondition == StopCondition::FilesChecked) && hasMetadata() && !isChecking()) + return; + + m_stopCondition = stopCondition; +} + bool TorrentImpl::isMoveInProgress() const { return m_storageIsMoving; diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 7a8e69539..d03b319e8 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -227,6 +227,9 @@ namespace BitTorrent void clearPeers() override; bool setMetadata(const TorrentInfo &torrentInfo) override; + StopCondition stopCondition() const override; + void setStopCondition(StopCondition stopCondition) override; + QString createMagnetURI() const override; nonstd::expected exportToBuffer() const override; nonstd::expected exportToFile(const Path &path) const override; @@ -332,6 +335,7 @@ namespace BitTorrent bool m_hasFirstLastPiecePriority = false; bool m_useAutoTMM; bool m_isStopped; + StopCondition m_stopCondition; bool m_unchecked = false; diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 9324c7499..c6049fded 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -218,6 +218,24 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP m_ui->downloadPath->setMaxVisibleItems(20); m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused())); + m_ui->stopConditionComboBox->setToolTip( + u"

" + tr("None") + u" - " + tr("No stop condition is set.") + u"

" + + tr("Metadata received") + u" - " + tr("Torrent will stop after metadata is received.") + + u" " + tr("Torrents that have metadata initially aren't affected.") + u"

" + + tr("Files checked") + u" - " + tr("Torrent will stop after files are initially checked.") + + u" " + tr("This will also download metadata if it wasn't there initially.") + u"

"); + m_ui->stopConditionComboBox->setItemData(0, QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)); + m_ui->stopConditionComboBox->setItemData(1, QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived)); + m_ui->stopConditionComboBox->setItemData(2, QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked)); + m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData( + QVariant::fromValue(m_torrentParams.stopCondition.value_or(session->torrentStopCondition())))); + m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked()); + m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked()); + connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked) + { + m_ui->stopConditionLabel->setEnabled(checked); + m_ui->stopConditionComboBox->setEnabled(checked); + }); m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1); @@ -872,6 +890,7 @@ void AddNewTorrentDialog::accept() m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities(); m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked(); + m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value(); m_torrentParams.contentLayout = static_cast(m_ui->contentLayoutComboBox->currentIndex()); m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked(); diff --git a/src/gui/addnewtorrentdialog.ui b/src/gui/addnewtorrentdialog.ui index 5e484108c..3b8926ee9 100644 --- a/src/gui/addnewtorrentdialog.ui +++ b/src/gui/addnewtorrentdialog.ui @@ -203,22 +203,58 @@ - - - - When checked, the .torrent file will not be deleted regardless of the settings at the "Download" page of the Options dialog - + + - Do not delete .torrent file + Start torrent - - + + + + - Download first and last pieces first + Stop condition: + + + + + + + 0 + + + None + + + + + Metadata received + + + + + Files checked + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + @@ -227,32 +263,29 @@ - + Download in sequential order - - + + - Start torrent + Download first and last pieces first - - - - Qt::Horizontal + + + + When checked, the .torrent file will not be deleted regardless of the settings at the "Download" page of the Options dialog - - - 40 - 20 - + + Do not delete .torrent file - + diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index e86c81b89..ea6befb74 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -525,6 +525,19 @@ void OptionsDialog::loadDownloadsTabOptions() m_ui->contentLayoutComboBox->setCurrentIndex(static_cast(session->torrentContentLayout())); m_ui->checkStartPaused->setChecked(session->isAddTorrentPaused()); + m_ui->stopConditionComboBox->setToolTip( + u"

" + tr("None") + u" - " + tr("No stop condition is set.") + u"

" + + tr("Metadata received") + u" - " + tr("Torrent will stop after metadata is received.") + + u" " + tr("Torrents that have metadata initially aren't affected.") + u"

" + + tr("Files checked") + u" - " + tr("Torrent will stop after files are initially checked.") + + u" " + tr("This will also download metadata if it wasn't there initially.") + u"

"); + m_ui->stopConditionComboBox->setItemData(0, QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)); + m_ui->stopConditionComboBox->setItemData(1, QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived)); + m_ui->stopConditionComboBox->setItemData(2, QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked)); + m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(session->torrentStopCondition()))); + m_ui->stopConditionLabel->setEnabled(!m_ui->checkStartPaused->isChecked()); + m_ui->stopConditionComboBox->setEnabled(!m_ui->checkStartPaused->isChecked()); + const TorrentFileGuard::AutoDeleteMode autoDeleteMode = TorrentFileGuard::autoDeleteMode(); m_ui->deleteTorrentBox->setChecked(autoDeleteMode != TorrentFileGuard::Never); m_ui->deleteCancelledTorrentBox->setChecked(autoDeleteMode == TorrentFileGuard::Always); @@ -633,6 +646,12 @@ void OptionsDialog::loadDownloadsTabOptions() connect(m_ui->contentLayoutComboBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkStartPaused, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); + connect(m_ui->checkStartPaused, &QAbstractButton::toggled, this, [this](const bool checked) + { + m_ui->stopConditionLabel->setEnabled(!checked); + m_ui->stopConditionComboBox->setEnabled(!checked); + }); + connect(m_ui->stopConditionComboBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->deleteTorrentBox, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->deleteCancelledTorrentBox, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); @@ -692,6 +711,7 @@ void OptionsDialog::saveDownloadsTabOptions() const session->setTorrentContentLayout(static_cast(m_ui->contentLayoutComboBox->currentIndex())); session->setAddTorrentPaused(addTorrentsInPause()); + session->setTorrentStopCondition(m_ui->stopConditionComboBox->currentData().value()); TorrentFileGuard::setAutoDeleteMode(!m_ui->deleteTorrentBox->isChecked() ? TorrentFileGuard::Never : !m_ui->deleteCancelledTorrentBox->isChecked() ? TorrentFileGuard::IfAdded : TorrentFileGuard::Always); diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index 284107c85..e85f0e1cc 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -839,6 +839,52 @@ + + + + + + Torrent stop condition: + + + + + + + 0 + + + + None + + + + + Metadata received + + + + + Files checked + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -3555,6 +3601,7 @@ Use ';' to split multiple entries. Can use wildcard '*'. checkUseCustomTheme customThemeFilePath checkStartPaused + stopConditionComboBox spinPort checkUPnP textWebUiUsername diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index 192cecac4..72483e09d 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -665,6 +665,11 @@ void TorrentsController::addAction() const int seedingTimeLimit = parseInt(params()[u"seedingTimeLimit"_qs]).value_or(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME); const std::optional autoTMM = parseBool(params()[u"autoTMM"_qs]); + const QString stopConditionParam = params()[u"stopCondition"_qs]; + const std::optional stopCondition = (!stopConditionParam.isEmpty() + ? Utils::String::toEnum(stopConditionParam, BitTorrent::Torrent::StopCondition::None) + : std::optional {}); + const QString contentLayoutParam = params()[u"contentLayout"_qs]; const std::optional contentLayout = (!contentLayoutParam.isEmpty() ? Utils::String::toEnum(contentLayoutParam, BitTorrent::TorrentContentLayout::Original) @@ -693,6 +698,7 @@ void TorrentsController::addAction() addTorrentParams.sequential = seqDownload; addTorrentParams.firstLastPiecePriority = firstLastPiece; addTorrentParams.addPaused = addPaused; + addTorrentParams.stopCondition = stopCondition; addTorrentParams.contentLayout = contentLayout; addTorrentParams.savePath = Path(savepath); addTorrentParams.downloadPath = Path(downloadPath); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index f5b0034fd..cd89f6011 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -52,7 +52,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 8, 14}; +inline const Utils::Version<3, 2> API_VERSION {2, 8, 15}; class APIController; class AuthController;