From 35e18498d9bde2292f4c0be793f0870385e54770 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 15 Jul 2023 06:14:42 -0400 Subject: [PATCH] Add option to stop seeding when torrent has been inactive PR #19294. Closes #533. Closes #8073. Closes #15939. --- src/base/bittorrent/addtorrentparams.cpp | 7 +- src/base/bittorrent/addtorrentparams.h | 1 + .../bittorrent/bencoderesumedatastorage.cpp | 2 + src/base/bittorrent/dbresumedatastorage.cpp | 15 ++- src/base/bittorrent/loadtorrentparams.h | 1 + src/base/bittorrent/session.h | 2 + src/base/bittorrent/sessionimpl.cpp | 69 +++++++++++- src/base/bittorrent/sessionimpl.h | 4 + src/base/bittorrent/torrent.cpp | 4 + src/base/bittorrent/torrent.h | 7 ++ src/base/bittorrent/torrentimpl.cpp | 43 +++++++- src/base/bittorrent/torrentimpl.h | 4 + src/gui/optionsdialog.cpp | 30 ++++- src/gui/optionsdialog.h | 1 + src/gui/optionsdialog.ui | 103 +++++++++++------- src/gui/torrentcreatordialog.cpp | 1 + src/gui/torrentoptionsdialog.cpp | 45 +++++++- src/gui/torrentoptionsdialog.h | 2 + src/gui/torrentoptionsdialog.ui | 29 ++++- src/webui/api/appcontroller.cpp | 7 ++ src/webui/api/serialize/serialize_torrent.cpp | 2 + src/webui/api/serialize/serialize_torrent.h | 2 + src/webui/api/torrentscontroller.cpp | 8 +- src/webui/www/private/scripts/mocha-init.js | 3 +- src/webui/www/private/shareratio.html | 50 ++++++--- src/webui/www/private/views/preferences.html | 30 ++++- 26 files changed, 393 insertions(+), 79 deletions(-) diff --git a/src/base/bittorrent/addtorrentparams.cpp b/src/base/bittorrent/addtorrentparams.cpp index 5037cd42e..e78939232 100644 --- a/src/base/bittorrent/addtorrentparams.cpp +++ b/src/base/bittorrent/addtorrentparams.cpp @@ -50,6 +50,7 @@ const QString PARAM_AUTOTMM = u"use_auto_tmm"_s; const QString PARAM_UPLOADLIMIT = u"upload_limit"_s; const QString PARAM_DOWNLOADLIMIT = u"download_limit"_s; const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_s; +const QString PARAM_INACTIVESEEDINGTIMELIMIT = u"inactive_seeding_time_limit"_s; const QString PARAM_RATIOLIMIT = u"ratio_limit"_s; namespace @@ -107,14 +108,14 @@ bool BitTorrent::operator==(const AddTorrentParams &lhs, const AddTorrentParams lhs.addToQueueTop, lhs.addPaused, lhs.stopCondition, lhs.filePaths, lhs.filePriorities, lhs.skipChecking, lhs.contentLayout, lhs.useAutoTMM, lhs.uploadLimit, - lhs.downloadLimit, lhs.seedingTimeLimit, lhs.ratioLimit) + lhs.downloadLimit, lhs.seedingTimeLimit, lhs.inactiveSeedingTimeLimit, lhs.ratioLimit) == std::tie(rhs.name, rhs.category, rhs.tags, rhs.savePath, rhs.useDownloadPath, rhs.downloadPath, rhs.sequential, rhs.firstLastPiecePriority, rhs.addForced, rhs.addToQueueTop, rhs.addPaused, rhs.stopCondition, rhs.filePaths, rhs.filePriorities, rhs.skipChecking, rhs.contentLayout, rhs.useAutoTMM, rhs.uploadLimit, - rhs.downloadLimit, rhs.seedingTimeLimit, rhs.ratioLimit); + rhs.downloadLimit, rhs.seedingTimeLimit, rhs.inactiveSeedingTimeLimit, rhs.ratioLimit); } BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject &jsonObj) @@ -134,6 +135,7 @@ BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1); params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1); params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME); + params.inactiveSeedingTimeLimit = jsonObj.value(PARAM_INACTIVESEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME); params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO); return params; @@ -152,6 +154,7 @@ QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams ¶ms {PARAM_UPLOADLIMIT, params.uploadLimit}, {PARAM_DOWNLOADLIMIT, params.downloadLimit}, {PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit}, + {PARAM_INACTIVESEEDINGTIMELIMIT, params.inactiveSeedingTimeLimit}, {PARAM_RATIOLIMIT, params.ratioLimit} }; diff --git a/src/base/bittorrent/addtorrentparams.h b/src/base/bittorrent/addtorrentparams.h index 66efe527c..63ec0e4e7 100644 --- a/src/base/bittorrent/addtorrentparams.h +++ b/src/base/bittorrent/addtorrentparams.h @@ -67,6 +67,7 @@ namespace BitTorrent int uploadLimit = -1; int downloadLimit = -1; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; + int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; }; diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index eeb1a8cc0..4e6d0e46e 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -219,6 +219,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus"); torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority"); torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME); + torrentParams.inactiveSeedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-inactiveSeedingTimeLimit", Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME); torrentParams.savePath = Profile::instance()->fromPortablePath( Path(fromLTString(resumeDataRoot.dict_find_string_value("qBt-savePath")))); @@ -389,6 +390,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co data["qBt-ratioLimit"] = static_cast(resumeData.ratioLimit * 1000); data["qBt-seedingTimeLimit"] = resumeData.seedingTimeLimit; + data["qBt-inactiveSeedingTimeLimit"] = resumeData.inactiveSeedingTimeLimit; data["qBt-category"] = resumeData.category.toStdString(); data["qBt-tags"] = setToEntryList(resumeData.tags); data["qBt-name"] = resumeData.name.toStdString(); diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 619ce97b3..e625b23fa 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -66,7 +66,7 @@ namespace { const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s; - const int DB_VERSION = 4; + const int DB_VERSION = 5; const QString DB_TABLE_META = u"meta"_s; const QString DB_TABLE_TORRENTS = u"torrents"_s; @@ -135,6 +135,7 @@ namespace const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout"); const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit"); const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_limit"); + const Column DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT = makeColumn("inactive_seeding_time_limit"); const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn("has_outer_pieces_priority"); const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status"); const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode"); @@ -228,6 +229,7 @@ namespace resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool(); resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0; resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt(); + resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt(); resumeData.contentLayout = Utils::String::toEnum( query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original); resumeData.operatingMode = Utils::String::toEnum( @@ -527,6 +529,7 @@ void BitTorrent::DBResumeDataStorage::createDB() const makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, "TEXT NOT NULL"), makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"), + makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"), @@ -606,6 +609,14 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const throw RuntimeError(query.lastError().text()); } + if (fromVersion <= 4) + { + const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s + .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL")); + 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)) throw RuntimeError(query.lastError().text()); @@ -782,6 +793,7 @@ namespace DB_COLUMN_CONTENT_LAYOUT, DB_COLUMN_RATIO_LIMIT, DB_COLUMN_SEEDING_TIME_LIMIT, + DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, DB_COLUMN_HAS_SEED_STATUS, DB_COLUMN_OPERATING_MODE, @@ -840,6 +852,7 @@ namespace query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout)); query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast(m_resumeData.ratioLimit * 1000)); query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit); + query.bindValue(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.placeholder, m_resumeData.inactiveSeedingTimeLimit); query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, m_resumeData.firstLastPiecePriority); query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, m_resumeData.hasFinishedStatus); query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode)); diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index 68932c609..97e091f9a 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -60,5 +60,6 @@ namespace BitTorrent qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; + int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; }; } diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index e2d166399..ba4e23f44 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -203,6 +203,8 @@ namespace BitTorrent virtual void setGlobalMaxRatio(qreal ratio) = 0; virtual int globalMaxSeedingMinutes() const = 0; virtual void setGlobalMaxSeedingMinutes(int minutes) = 0; + virtual int globalMaxInactiveSeedingMinutes() const = 0; + virtual void setGlobalMaxInactiveSeedingMinutes(int minutes) = 0; virtual bool isDHTEnabled() const = 0; virtual void setDHTEnabled(bool enabled) = 0; virtual bool isLSDEnabled() const = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 58f687ba4..f09776120 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -474,6 +474,7 @@ SessionImpl::SessionImpl(QObject *parent) , m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s)) , m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;}) , m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1)) + , m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1)) , m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false) , m_isAddTorrentPaused(BITTORRENT_SESSION_KEY(u"AddTorrentPaused"_s), false) , m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None) @@ -1118,6 +1119,22 @@ void SessionImpl::setGlobalMaxSeedingMinutes(int minutes) } } +int SessionImpl::globalMaxInactiveSeedingMinutes() const +{ + return m_globalMaxInactiveSeedingMinutes; +} + +void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes) +{ + minutes = std::max(minutes, -1); + + if (minutes != globalMaxInactiveSeedingMinutes()) + { + m_globalMaxInactiveSeedingMinutes = minutes; + updateSeedingLimitTimer(); + } +} + void SessionImpl::applyBandwidthLimits() { lt::settings_pack settingsPack; @@ -2213,6 +2230,47 @@ void SessionImpl::processShareLimits() } } } + + if (torrent->inactiveSeedingTimeLimit() != Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) + { + const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60; + int inactiveSeedingTimeLimit = torrent->inactiveSeedingTimeLimit(); + if (inactiveSeedingTimeLimit == Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME) + { + // If Global Seeding Time Limit is really set... + inactiveSeedingTimeLimit = globalMaxInactiveSeedingMinutes(); + } + + if (inactiveSeedingTimeLimit >= 0) + { + if ((inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit)) + { + const QString description = tr("Torrent reached the inactive seeding time limit."); + const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name()); + + if (m_maxRatioAction == Remove) + { + LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent."), torrentName)); + deleteTorrent(torrent->id()); + } + else if (m_maxRatioAction == DeleteFiles) + { + LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removed torrent and deleted its content."), torrentName)); + deleteTorrent(torrent->id(), DeleteTorrentAndFiles); + } + else if ((m_maxRatioAction == Pause) && !torrent->isPaused()) + { + torrent->pause(); + LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent paused."), torrentName)); + } + else if ((m_maxRatioAction == EnableSuperSeeding) && !torrent->isPaused() && !torrent->superSeeding()) + { + torrent->setSuperSeeding(true); + LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName)); + } + } + } + } } } } @@ -4842,7 +4900,8 @@ bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const void SessionImpl::updateSeedingLimitTimer() { if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit() - && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()) + && (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit() + && (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit()) { if (m_seedingLimitTimer->isActive()) m_seedingLimitTimer->stop(); @@ -5206,6 +5265,14 @@ bool SessionImpl::hasPerTorrentSeedingTimeLimit() const }); } +bool SessionImpl::hasPerTorrentInactiveSeedingTimeLimit() const +{ + return std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent) + { + return (torrent->inactiveSeedingTimeLimit() >= 0); + }); +} + void SessionImpl::configureDeferred() { if (m_deferredConfigureScheduled) diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 227ae7d5c..f75879019 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -182,6 +182,8 @@ namespace BitTorrent void setGlobalMaxRatio(qreal ratio) override; int globalMaxSeedingMinutes() const override; void setGlobalMaxSeedingMinutes(int minutes) override; + int globalMaxInactiveSeedingMinutes() const override; + void setGlobalMaxInactiveSeedingMinutes(int minutes) override; bool isDHTEnabled() const override; void setDHTEnabled(bool enabled) override; bool isLSDEnabled() const override; @@ -513,6 +515,7 @@ namespace BitTorrent bool hasPerTorrentRatioLimit() const; bool hasPerTorrentSeedingTimeLimit() const; + bool hasPerTorrentInactiveSeedingTimeLimit() const; // Session configuration Q_INVOKABLE void configure(); @@ -660,6 +663,7 @@ namespace BitTorrent CachedSettingValue m_additionalTrackers; CachedSettingValue m_globalMaxRatio; CachedSettingValue m_globalMaxSeedingMinutes; + CachedSettingValue m_globalMaxInactiveSeedingMinutes; CachedSettingValue m_isAddTorrentToQueueTop; CachedSettingValue m_isAddTorrentPaused; CachedSettingValue m_torrentStopCondition; diff --git a/src/base/bittorrent/torrent.cpp b/src/base/bittorrent/torrent.cpp index e478eabfd..7df2d8dc3 100644 --- a/src/base/bittorrent/torrent.cpp +++ b/src/base/bittorrent/torrent.cpp @@ -52,8 +52,12 @@ namespace BitTorrent const int Torrent::USE_GLOBAL_SEEDING_TIME = -2; const int Torrent::NO_SEEDING_TIME_LIMIT = -1; + const int Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME = -2; + const int Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT = -1; + const qreal Torrent::MAX_RATIO = 9999; const int Torrent::MAX_SEEDING_TIME = 525600; + const int Torrent::MAX_INACTIVE_SEEDING_TIME = 525600; TorrentID Torrent::id() const { diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index f501dc175..49a332515 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -126,8 +126,12 @@ namespace BitTorrent static const int USE_GLOBAL_SEEDING_TIME; static const int NO_SEEDING_TIME_LIMIT; + static const int USE_GLOBAL_INACTIVE_SEEDING_TIME; + static const int NO_INACTIVE_SEEDING_TIME_LIMIT; + static const qreal MAX_RATIO; static const int MAX_SEEDING_TIME; + static const int MAX_INACTIVE_SEEDING_TIME; using TorrentContentHandler::TorrentContentHandler; @@ -210,6 +214,7 @@ namespace BitTorrent virtual QDateTime addedTime() const = 0; virtual qreal ratioLimit() const = 0; virtual int seedingTimeLimit() const = 0; + virtual int inactiveSeedingTimeLimit() const = 0; virtual PathList filePaths() const = 0; @@ -264,6 +269,7 @@ namespace BitTorrent virtual qreal distributedCopies() const = 0; virtual qreal maxRatio() const = 0; virtual int maxSeedingTime() const = 0; + virtual int maxInactiveSeedingTime() const = 0; virtual qreal realRatio() const = 0; virtual int uploadPayloadRate() const = 0; virtual int downloadPayloadRate() const = 0; @@ -283,6 +289,7 @@ namespace BitTorrent virtual void forceRecheck() = 0; virtual void setRatioLimit(qreal limit) = 0; virtual void setSeedingTimeLimit(int limit) = 0; + virtual void setInactiveSeedingTimeLimit(int limit) = 0; virtual void setUploadLimit(int limit) = 0; virtual void setDownloadLimit(int limit) = 0; virtual void setSuperSeeding(bool enable) = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index de649548f..86a4e1147 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -251,6 +251,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession , m_tags(params.tags) , m_ratioLimit(params.ratioLimit) , m_seedingTimeLimit(params.seedingTimeLimit) + , m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit) , m_operatingMode(params.operatingMode) , m_contentLayout(params.contentLayout) , m_hasFinishedStatus(params.hasFinishedStatus) @@ -862,6 +863,11 @@ int TorrentImpl::seedingTimeLimit() const return m_seedingTimeLimit; } +int TorrentImpl::inactiveSeedingTimeLimit() const +{ + return m_inactiveSeedingTimeLimit; +} + Path TorrentImpl::filePath(const int index) const { Q_ASSERT(index >= 0); @@ -1165,7 +1171,8 @@ qlonglong TorrentImpl::eta() const { const qreal maxRatioValue = maxRatio(); const int maxSeedingTimeValue = maxSeedingTime(); - if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA; + const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime(); + if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA; qlonglong ratioEta = MAX_ETA; @@ -1188,7 +1195,15 @@ qlonglong TorrentImpl::eta() const seedingTimeEta = 0; } - return std::min(ratioEta, seedingTimeEta); + qlonglong inactiveSeedingTimeEta = MAX_ETA; + + if (maxInactiveSeedingTimeValue >= 0) + { + inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity(); + inactiveSeedingTimeEta = std::max(inactiveSeedingTimeEta, 0); + } + + return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta}); } if (!speedAverage.download) return MAX_ETA; @@ -1385,6 +1400,14 @@ int TorrentImpl::maxSeedingTime() const return m_seedingTimeLimit; } +int TorrentImpl::maxInactiveSeedingTime() const +{ + if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME) + return m_session->globalMaxInactiveSeedingMinutes(); + + return m_inactiveSeedingTimeLimit; +} + qreal TorrentImpl::realRatio() const { const int64_t upload = m_nativeStatus.all_time_upload; @@ -2014,6 +2037,7 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms) resumeData.contentLayout = m_contentLayout; resumeData.ratioLimit = m_ratioLimit; resumeData.seedingTimeLimit = m_seedingTimeLimit; + resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit; resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority; resumeData.hasFinishedStatus = m_hasFinishedStatus; resumeData.stopped = m_isStopped; @@ -2406,6 +2430,21 @@ void TorrentImpl::setSeedingTimeLimit(int limit) } } +void TorrentImpl::setInactiveSeedingTimeLimit(int limit) +{ + if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME) + limit = NO_INACTIVE_SEEDING_TIME_LIMIT; + else if (limit > MAX_INACTIVE_SEEDING_TIME) + limit = MAX_SEEDING_TIME; + + if (m_inactiveSeedingTimeLimit != limit) + { + m_inactiveSeedingTimeLimit = limit; + m_session->handleTorrentNeedSaveResumeData(this); + m_session->handleTorrentShareLimitChanged(this); + } +} + void TorrentImpl::setUploadLimit(const int limit) { const int cleanValue = cleanLimitValue(limit); diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index 685944293..2249f751e 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -138,6 +138,7 @@ namespace BitTorrent QDateTime addedTime() const override; qreal ratioLimit() const override; int seedingTimeLimit() const override; + int inactiveSeedingTimeLimit() const override; Path filePath(int index) const override; Path actualFilePath(int index) const override; @@ -198,6 +199,7 @@ namespace BitTorrent qreal distributedCopies() const override; qreal maxRatio() const override; int maxSeedingTime() const override; + int maxInactiveSeedingTime() const override; qreal realRatio() const override; int uploadPayloadRate() const override; int downloadPayloadRate() const override; @@ -220,6 +222,7 @@ namespace BitTorrent void prioritizeFiles(const QVector &priorities) override; void setRatioLimit(qreal limit) override; void setSeedingTimeLimit(int limit) override; + void setInactiveSeedingTimeLimit(int limit) override; void setUploadLimit(int limit) override; void setDownloadLimit(int limit) override; void setSuperSeeding(bool enable) override; @@ -345,6 +348,7 @@ namespace BitTorrent TagSet m_tags; qreal m_ratioLimit; int m_seedingTimeLimit; + int m_inactiveSeedingTimeLimit; TorrentOperatingMode m_operatingMode; TorrentContentLayout m_contentLayout; bool m_hasFinishedStatus; diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index 9df8eb96b..c2e7efe18 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -1048,7 +1048,20 @@ void OptionsDialog::loadBittorrentTabOptions() m_ui->checkMaxSeedingMinutes->setChecked(false); m_ui->spinMaxSeedingMinutes->setEnabled(false); } - m_ui->comboRatioLimitAct->setEnabled((session->globalMaxSeedingMinutes() >= 0) || (session->globalMaxRatio() >= 0.)); + if (session->globalMaxInactiveSeedingMinutes() >= 0) + { + // Enable + m_ui->checkMaxInactiveSeedingMinutes->setChecked(true); + m_ui->spinMaxInactiveSeedingMinutes->setEnabled(true); + m_ui->spinMaxInactiveSeedingMinutes->setValue(session->globalMaxInactiveSeedingMinutes()); + } + else + { + // Disable + m_ui->checkMaxInactiveSeedingMinutes->setChecked(false); + m_ui->spinMaxInactiveSeedingMinutes->setEnabled(false); + } + m_ui->comboRatioLimitAct->setEnabled((session->globalMaxSeedingMinutes() >= 0) || (session->globalMaxRatio() >= 0.) || (session->globalMaxInactiveSeedingMinutes() >= 0)); const QHash actIndex = { @@ -1088,6 +1101,10 @@ void OptionsDialog::loadBittorrentTabOptions() connect(m_ui->checkMaxSeedingMinutes, &QAbstractButton::toggled, this, &ThisType::toggleComboRatioLimitAct); connect(m_ui->checkMaxSeedingMinutes, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->spinMaxSeedingMinutes, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); + connect(m_ui->checkMaxInactiveSeedingMinutes, &QAbstractButton::toggled, m_ui->spinMaxInactiveSeedingMinutes, &QWidget::setEnabled); + connect(m_ui->checkMaxInactiveSeedingMinutes, &QAbstractButton::toggled, this, &ThisType::toggleComboRatioLimitAct); + connect(m_ui->checkMaxInactiveSeedingMinutes, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); + connect(m_ui->spinMaxInactiveSeedingMinutes, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkEnableAddTrackers, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->textTrackers, &QPlainTextEdit::textChanged, this, &ThisType::enableApplyButton); @@ -1116,6 +1133,7 @@ void OptionsDialog::saveBittorrentTabOptions() const session->setGlobalMaxRatio(getMaxRatio()); session->setGlobalMaxSeedingMinutes(getMaxSeedingMinutes()); + session->setGlobalMaxInactiveSeedingMinutes(getMaxInactiveSeedingMinutes()); const QVector actIndex = { Pause, @@ -1443,6 +1461,14 @@ int OptionsDialog::getMaxSeedingMinutes() const return -1; } +// Return Inactive Seeding Minutes +int OptionsDialog::getMaxInactiveSeedingMinutes() const +{ + return m_ui->checkMaxInactiveSeedingMinutes->isChecked() + ? m_ui->spinMaxInactiveSeedingMinutes->value() + : -1; +} + // Return max connections number int OptionsDialog::getMaxConnections() const { @@ -1547,7 +1573,7 @@ void OptionsDialog::enableApplyButton() void OptionsDialog::toggleComboRatioLimitAct() { // Verify if the share action button must be enabled - m_ui->comboRatioLimitAct->setEnabled(m_ui->checkMaxRatio->isChecked() || m_ui->checkMaxSeedingMinutes->isChecked()); + m_ui->comboRatioLimitAct->setEnabled(m_ui->checkMaxRatio->isChecked() || m_ui->checkMaxSeedingMinutes->isChecked() || m_ui->checkMaxInactiveSeedingMinutes->isChecked()); } void OptionsDialog::adjustProxyOptions() diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index 5f56e7545..dfb5a75c8 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -166,6 +166,7 @@ private: int getEncryptionSetting() const; qreal getMaxRatio() const; int getMaxSeedingMinutes() const; + int getMaxInactiveSeedingMinutes() const; // Proxy options bool isProxyEnabled() const; QString getProxyIp() const; diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index cc00cfed3..08cdac51a 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -2899,8 +2899,51 @@ Disable encryption: Only connect to peers without protocol encryption Seeding Limits - - + + + + + When ratio reaches + + + + + + + false + + + 9998.000000000000000 + + + 0.050000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + When total seeding time reaches + + + + false @@ -2917,9 +2960,25 @@ Disable encryption: Only connect to peers without protocol encryption - + - When seeding time reaches + When inactive seeding time reaches + + + + + + + false + + + min + + + 9999999 + + + 1440 @@ -2960,42 +3019,6 @@ Disable encryption: Only connect to peers without protocol encryption - - - - When ratio reaches - - - - - - - false - - - 9998.000000000000000 - - - 0.050000000000000 - - - 1.000000000000000 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - diff --git a/src/gui/torrentcreatordialog.cpp b/src/gui/torrentcreatordialog.cpp index 9d816213e..83609e6d9 100644 --- a/src/gui/torrentcreatordialog.cpp +++ b/src/gui/torrentcreatordialog.cpp @@ -266,6 +266,7 @@ void TorrentCreatorDialog::handleCreationSuccess(const Path &path, const Path &b { params.ratioLimit = BitTorrent::Torrent::NO_RATIO_LIMIT; params.seedingTimeLimit = BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT; + params.inactiveSeedingTimeLimit = BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT; } params.useAutoTMM = false; // otherwise if it is on by default, it will overwrite `savePath` to the default save path diff --git a/src/gui/torrentoptionsdialog.cpp b/src/gui/torrentoptionsdialog.cpp index 0ddca35cb..5d6ae2552 100644 --- a/src/gui/torrentoptionsdialog.cpp +++ b/src/gui/torrentoptionsdialog.cpp @@ -82,6 +82,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorratioLimit(); const int firstTorrentSeedingTime = torrents[0]->seedingTimeLimit(); + const int firstTorrentInactiveSeedingTime = torrents[0]->inactiveSeedingTimeLimit(); const bool isFirstTorrentDHTDisabled = torrents[0]->isDHTDisabled(); const bool isFirstTorrentPEXDisabled = torrents[0]->isPEXDisabled(); @@ -154,6 +156,11 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorseedingTimeLimit() != firstTorrentSeedingTime) allSameSeedingTime = false; } + if (allSameInactiveSeedingTime) + { + if (torrent->inactiveSeedingTimeLimit() != firstTorrentInactiveSeedingTime) + allSameInactiveSeedingTime = false; + } if (allTorrentsArePrivate) { if (!torrent->isPrivate()) @@ -280,7 +287,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorradioUseGlobalShareLimits->setChecked(false); m_ui->radioNoLimit->setChecked(false); @@ -291,7 +298,8 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorradioUseGlobalShareLimits->setChecked(true); } else if ((firstTorrentRatio == BitTorrent::Torrent::NO_RATIO_LIMIT) - && (firstTorrentSeedingTime == BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT)) + && (firstTorrentSeedingTime == BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT) + && (firstTorrentInactiveSeedingTime == BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT)) { m_ui->radioNoLimit->setChecked(true); } @@ -302,14 +310,19 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorcheckMaxRatio->setChecked(true); if (firstTorrentSeedingTime >= 0) m_ui->checkMaxTime->setChecked(true); + if (firstTorrentInactiveSeedingTime >= 0) + m_ui->checkMaxInactiveTime->setChecked(true); } const qreal maxRatio = (allSameRatio && (firstTorrentRatio >= 0)) ? firstTorrentRatio : session->globalMaxRatio(); const int maxSeedingTime = (allSameSeedingTime && (firstTorrentSeedingTime >= 0)) ? firstTorrentSeedingTime : session->globalMaxSeedingMinutes(); + const int maxInactiveSeedingTime = (allSameInactiveSeedingTime && (firstTorrentInactiveSeedingTime >= 0)) + ? firstTorrentInactiveSeedingTime : session->globalMaxInactiveSeedingMinutes(); m_ui->spinRatioLimit->setValue(maxRatio); m_ui->spinTimeLimit->setValue(maxSeedingTime); + m_ui->spinInactiveTimeLimit->setValue(maxInactiveSeedingTime); if (!allTorrentsArePrivate) { @@ -360,6 +373,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorcomboCategory->currentText(), getRatio(), getSeedingTime(), + getInactiveSeedingTime(), m_ui->spinUploadLimit->value(), m_ui->spinDownloadLimit->value(), m_ui->checkAutoTMM->checkState(), @@ -390,6 +404,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVectorcheckMaxRatio, &QCheckBox::toggled, m_ui->spinRatioLimit, &QWidget::setEnabled); connect(m_ui->checkMaxTime, &QCheckBox::toggled, m_ui->spinTimeLimit, &QWidget::setEnabled); + connect(m_ui->checkMaxInactiveTime, &QCheckBox::toggled, m_ui->spinInactiveTimeLimit, &QSpinBox::setEnabled); connect(m_ui->buttonGroup, &QButtonGroup::idClicked, this, &TorrentOptionsDialog::handleRatioTypeChanged); @@ -405,7 +420,8 @@ TorrentOptionsDialog::~TorrentOptionsDialog() void TorrentOptionsDialog::accept() { - if (m_ui->radioTorrentLimit->isChecked() && !m_ui->checkMaxRatio->isChecked() && !m_ui->checkMaxTime->isChecked()) + if (m_ui->radioTorrentLimit->isChecked() && !m_ui->checkMaxRatio->isChecked() + && !m_ui->checkMaxTime->isChecked() && !m_ui->checkMaxInactiveTime->isChecked()) { QMessageBox::critical(this, tr("No share limit method selected"), tr("Please select a limit method first")); return; @@ -462,6 +478,10 @@ void TorrentOptionsDialog::accept() if (m_initialValues.seedingTime != seedingTimeLimit) torrent->setSeedingTimeLimit(seedingTimeLimit); + const int inactiveSeedingTimeLimit = getInactiveSeedingTime(); + if (m_initialValues.inactiveSeedingTime != inactiveSeedingTimeLimit) + torrent->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit); + if (!torrent->isPrivate()) { if (m_initialValues.disableDHT != m_ui->checkDisableDHT->checkState()) @@ -509,6 +529,20 @@ int TorrentOptionsDialog::getSeedingTime() const return m_ui->spinTimeLimit->value(); } +int TorrentOptionsDialog::getInactiveSeedingTime() const +{ + if (m_ui->buttonGroup->checkedId() == -1) // No radio button is selected + return MIXED_SHARE_LIMITS; + + if (m_ui->radioUseGlobalShareLimits->isChecked()) + return BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; + + if (m_ui->radioNoLimit->isChecked() || !m_ui->checkMaxInactiveTime->isChecked()) + return BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT; + + return m_ui->spinInactiveTimeLimit->value(); +} + void TorrentOptionsDialog::handleCategoryChanged(const int index) { Q_UNUSED(index); @@ -588,7 +622,8 @@ void TorrentOptionsDialog::handleUseDownloadPathChanged() void TorrentOptionsDialog::handleRatioTypeChanged() { - if ((m_initialValues.ratio == MIXED_SHARE_LIMITS) || (m_initialValues.seedingTime == MIXED_SHARE_LIMITS)) + if ((m_initialValues.ratio == MIXED_SHARE_LIMITS) || (m_initialValues.seedingTime == MIXED_SHARE_LIMITS) + || (m_initialValues.inactiveSeedingTime == MIXED_SHARE_LIMITS)) { QAbstractButton *currentRadio = m_ui->buttonGroup->checkedButton(); if (currentRadio && (currentRadio == m_previousRadio)) @@ -603,9 +638,11 @@ void TorrentOptionsDialog::handleRatioTypeChanged() m_ui->checkMaxRatio->setEnabled(m_ui->radioTorrentLimit->isChecked()); m_ui->checkMaxTime->setEnabled(m_ui->radioTorrentLimit->isChecked()); + m_ui->checkMaxInactiveTime->setEnabled(m_ui->radioTorrentLimit->isChecked()); m_ui->spinRatioLimit->setEnabled(m_ui->radioTorrentLimit->isChecked() && m_ui->checkMaxRatio->isChecked()); m_ui->spinTimeLimit->setEnabled(m_ui->radioTorrentLimit->isChecked() && m_ui->checkMaxTime->isChecked()); + m_ui->spinInactiveTimeLimit->setEnabled(m_ui->radioTorrentLimit->isChecked() && m_ui->checkMaxInactiveTime->isChecked()); } void TorrentOptionsDialog::handleUpSpeedLimitChanged() diff --git a/src/gui/torrentoptionsdialog.h b/src/gui/torrentoptionsdialog.h index 3af01777f..d6d8222d7 100644 --- a/src/gui/torrentoptionsdialog.h +++ b/src/gui/torrentoptionsdialog.h @@ -73,6 +73,7 @@ private slots: private: qreal getRatio() const; int getSeedingTime() const; + int getInactiveSeedingTime() const; QVector m_torrentIDs; Ui::TorrentOptionsDialog *m_ui = nullptr; @@ -88,6 +89,7 @@ private: QString category; qreal ratio; int seedingTime; + int inactiveSeedingTime; int upSpeedLimit; int downSpeedLimit; Qt::CheckState autoTMM; diff --git a/src/gui/torrentoptionsdialog.ui b/src/gui/torrentoptionsdialog.ui index 65286133f..708a34c7a 100644 --- a/src/gui/torrentoptionsdialog.ui +++ b/src/gui/torrentoptionsdialog.ui @@ -188,10 +188,10 @@ - - + + - minutes + ratio @@ -208,6 +208,13 @@ + + + + total minutes + + + @@ -231,10 +238,20 @@ - - + + - ratio + inactive minutes + + + + + + + 525600 + + + 1440 diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 7c6096578..346e9a8d9 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -258,6 +258,8 @@ void AppController::preferencesAction() data[u"max_ratio"_s] = session->globalMaxRatio(); data[u"max_seeding_time_enabled"_s] = (session->globalMaxSeedingMinutes() >= 0.); data[u"max_seeding_time"_s] = session->globalMaxSeedingMinutes(); + data[u"max_inactive_seeding_time_enabled"_s] = (session->globalMaxInactiveSeedingMinutes() >= 0.); + data[u"max_inactive_seeding_time"_s] = session->globalMaxInactiveSeedingMinutes(); data[u"max_ratio_act"_s] = session->maxRatioAction(); // Add trackers data[u"add_trackers_enabled"_s] = session->isAddTrackersEnabled(); @@ -739,6 +741,11 @@ void AppController::setPreferencesAction() else session->setGlobalMaxSeedingMinutes(-1); } + if (hasKey(u"max_inactive_seeding_time_enabled"_s)) + { + session->setGlobalMaxInactiveSeedingMinutes(it.value().toBool() + ? m[u"max_inactive_seeding_time"_s].toInt() : -1); + } if (hasKey(u"max_ratio_act"_s)) session->setMaxRatioAction(static_cast(it.value().toInt())); // Add trackers diff --git a/src/webui/api/serialize/serialize_torrent.cpp b/src/webui/api/serialize/serialize_torrent.cpp index c82565bed..833f7c3e0 100644 --- a/src/webui/api/serialize/serialize_torrent.cpp +++ b/src/webui/api/serialize/serialize_torrent.cpp @@ -148,9 +148,11 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent) {KEY_TORRENT_AMOUNT_COMPLETED, torrent.completedSize()}, {KEY_TORRENT_MAX_RATIO, torrent.maxRatio()}, {KEY_TORRENT_MAX_SEEDING_TIME, torrent.maxSeedingTime()}, + {KEY_TORRENT_MAX_INACTIVE_SEEDING_TIME, torrent.maxInactiveSeedingTime()}, {KEY_TORRENT_RATIO, adjustRatio(torrent.realRatio())}, {KEY_TORRENT_RATIO_LIMIT, torrent.ratioLimit()}, {KEY_TORRENT_SEEDING_TIME_LIMIT, torrent.seedingTimeLimit()}, + {KEY_TORRENT_INACTIVE_SEEDING_TIME_LIMIT, torrent.inactiveSeedingTimeLimit()}, {KEY_TORRENT_LAST_SEEN_COMPLETE_TIME, torrent.lastSeenComplete().toSecsSinceEpoch()}, {KEY_TORRENT_AUTO_TORRENT_MANAGEMENT, torrent.isAutoTMMEnabled()}, {KEY_TORRENT_TIME_ACTIVE, torrent.activeTime()}, diff --git a/src/webui/api/serialize/serialize_torrent.h b/src/webui/api/serialize/serialize_torrent.h index 21e46b32a..881cdd7a6 100644 --- a/src/webui/api/serialize/serialize_torrent.h +++ b/src/webui/api/serialize/serialize_torrent.h @@ -79,8 +79,10 @@ inline const QString KEY_TORRENT_AMOUNT_LEFT = u"amount_left"_s; inline const QString KEY_TORRENT_AMOUNT_COMPLETED = u"completed"_s; inline const QString KEY_TORRENT_MAX_RATIO = u"max_ratio"_s; inline const QString KEY_TORRENT_MAX_SEEDING_TIME = u"max_seeding_time"_s; +inline const QString KEY_TORRENT_MAX_INACTIVE_SEEDING_TIME = u"max_inactive_seeding_time"_s; inline const QString KEY_TORRENT_RATIO_LIMIT = u"ratio_limit"_s; inline const QString KEY_TORRENT_SEEDING_TIME_LIMIT = u"seeding_time_limit"_s; +inline const QString KEY_TORRENT_INACTIVE_SEEDING_TIME_LIMIT = u"inactive_seeding_time_limit"_s; inline const QString KEY_TORRENT_LAST_SEEN_COMPLETE_TIME = u"seen_complete"_s; inline const QString KEY_TORRENT_LAST_ACTIVITY_TIME = u"last_activity"_s; inline const QString KEY_TORRENT_TOTAL_SIZE = u"total_size"_s; diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index c008acd87..57799f9e4 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -673,6 +673,7 @@ void TorrentsController::addAction() const int dlLimit = parseInt(params()[u"dlLimit"_s]).value_or(-1); const double ratioLimit = parseDouble(params()[u"ratioLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_RATIO); const int seedingTimeLimit = parseInt(params()[u"seedingTimeLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME); + const int inactiveSeedingTimeLimit = parseInt(params()[u"inactiveSeedingTimeLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME); const std::optional autoTMM = parseBool(params()[u"autoTMM"_s]); const QString stopConditionParam = params()[u"stopCondition"_s]; @@ -720,6 +721,7 @@ void TorrentsController::addAction() addTorrentParams.uploadLimit = upLimit; addTorrentParams.downloadLimit = dlLimit; addTorrentParams.seedingTimeLimit = seedingTimeLimit; + addTorrentParams.inactiveSeedingTimeLimit = inactiveSeedingTimeLimit; addTorrentParams.ratioLimit = ratioLimit; addTorrentParams.useAutoTMM = autoTMM; @@ -980,16 +982,18 @@ void TorrentsController::setDownloadLimitAction() void TorrentsController::setShareLimitsAction() { - requireParams({u"hashes"_s, u"ratioLimit"_s, u"seedingTimeLimit"_s}); + requireParams({u"hashes"_s, u"ratioLimit"_s, u"seedingTimeLimit"_s, u"inactiveSeedingTimeLimit"_s}); const qreal ratioLimit = params()[u"ratioLimit"_s].toDouble(); const qlonglong seedingTimeLimit = params()[u"seedingTimeLimit"_s].toLongLong(); + const qlonglong inactiveSeedingTimeLimit = params()[u"inactiveSeedingTimeLimit"_s].toLongLong(); const QStringList hashes = params()[u"hashes"_s].split(u'|'); - applyToTorrents(hashes, [ratioLimit, seedingTimeLimit](BitTorrent::Torrent *const torrent) + applyToTorrents(hashes, [ratioLimit, seedingTimeLimit, inactiveSeedingTimeLimit](BitTorrent::Torrent *const torrent) { torrent->setRatioLimit(ratioLimit); torrent->setSeedingTimeLimit(seedingTimeLimit); + torrent->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit); }); } diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index fbfa32c0d..217a3bb91 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -240,7 +240,8 @@ const initializeWindows = function() { for (let i = 0; i < hashes.length; ++i) { const hash = hashes[i]; const row = torrentsTable.rows[hash].full_data; - const origValues = row.ratio_limit + "|" + row.seeding_time_limit + "|" + row.max_ratio + "|" + row.max_seeding_time; + const origValues = row.ratio_limit + "|" + row.seeding_time_limit + "|" + row.inactive_seeding_time_limit + "|" + + row.max_ratio + "|" + row.max_seeding_time + "|" + row.max_inactive_seeding_time; // initialize value if (shareRatio === null) diff --git a/src/webui/www/private/shareratio.html b/src/webui/www/private/shareratio.html index 07183264e..897f0cb01 100644 --- a/src/webui/www/private/shareratio.html +++ b/src/webui/www/private/shareratio.html @@ -39,16 +39,19 @@ const values = { ratioLimit: window.qBittorrent.Misc.friendlyFloat(origValues[0], 2), seedingTimeLimit: parseInt(origValues[1]), - maxRatio: window.qBittorrent.Misc.friendlyFloat(origValues[2], 2), - maxSeedingTime: parseInt(origValues[3]) + inactiveSeedingTimeLimit: parseInt(origValues[2]), + maxRatio: window.qBittorrent.Misc.friendlyFloat(origValues[3], 2), + maxSeedingTime: parseInt(origValues[4]), + maxInactiveSeedingTime: parseInt(origValues[5]) }; // select default when orig values not passed. using double equals to compare string and int - if ((origValues[0] === "") || ((values.ratioLimit == UseGlobalLimit) && (values.seedingTimeLimit == UseGlobalLimit))) { + if ((origValues[0] === "") || ((values.ratioLimit == UseGlobalLimit) && (values.seedingTimeLimit == UseGlobalLimit)) + && (values.inactiveSeedingTimeLimit == UseGlobalLimit)) { // use default option setSelectedRadioValue('shareLimit', 'default'); } - else if ((values.maxRatio == NoLimit) && (values.maxSeedingTime == NoLimit)) { + else if ((values.maxRatio == NoLimit) && (values.maxSeedingTime == NoLimit) && (values.maxInactiveSeedingTime == NoLimit)) { setSelectedRadioValue('shareLimit', 'none'); // TODO set input boxes to *global* max ratio and seeding time } @@ -59,8 +62,12 @@ $('ratio').set('value', values.ratioLimit); } if (values.seedingTimeLimit >= 0) { - $('setMinutes').set('checked', true); - $('minutes').set('value', values.seedingTimeLimit); + $('setTotalMinutes').set('checked', true); + $('totalMinutes').set('value', values.seedingTimeLimit); + } + if (values.inactiveSeedingTimeLimit >= 0) { + $('setInactiveMinutes').set('checked', true); + $('inactiveMinutes').set('value', values.inactiveSeedingTimeLimit); } } @@ -77,16 +84,18 @@ const shareLimit = getSelectedRadioValue('shareLimit'); let ratioLimitValue = 0.00; let seedingTimeLimitValue = 0; + let inactiveSeedingTimeLimitValue = 0; if (shareLimit === 'default') { - ratioLimitValue = seedingTimeLimitValue = UseGlobalLimit; + ratioLimitValue = seedingTimeLimitValue = inactiveSeedingTimeLimitValue = UseGlobalLimit; } else if (shareLimit === 'none') { - ratioLimitValue = seedingTimeLimitValue = NoLimit; + ratioLimitValue = seedingTimeLimitValue = inactiveSeedingTimeLimitValue = NoLimit; } else if (shareLimit === 'custom') { ratioLimitValue = $('setRatio').get('checked') ? $('ratio').get('value') : -1; - seedingTimeLimitValue = $('setMinutes').get('checked') ? $('minutes').get('value') : -1; + seedingTimeLimitValue = $('setTotalMinutes').get('checked') ? $('totalMinutes').get('value') : -1; + inactiveSeedingTimeLimitValue = $('setInactiveMinutes').get('checked') ? $('inactiveMinutes').get('value') : -1; } else { return false; @@ -98,7 +107,8 @@ data: { hashes: hashesList.join('|'), ratioLimit: ratioLimitValue, - seedingTimeLimit: seedingTimeLimitValue + seedingTimeLimit: seedingTimeLimitValue, + inactiveSeedingTimeLimit: inactiveSeedingTimeLimitValue }, onComplete: function() { window.parent.closeWindows(); @@ -131,7 +141,8 @@ function shareLimitChanged() { const customShareLimit = getSelectedRadioValue('shareLimit') === 'custom'; $('setRatio').set('disabled', !customShareLimit); - $('setMinutes').set('disabled', !customShareLimit); + $('setTotalMinutes').set('disabled', !customShareLimit); + $('setInactiveMinutes').set('disabled', !customShareLimit); enableInputBoxes(); @@ -140,13 +151,15 @@ function enableInputBoxes() { $('ratio').set('disabled', ($('setRatio').get('disabled') || !$('setRatio').get('checked'))); - $('minutes').set('disabled', ($('setMinutes').get('disabled') || !$('setMinutes').get('checked'))); + $('totalMinutes').set('disabled', ($('setTotalMinutes').get('disabled') || !$('setTotalMinutes').get('checked'))); + $('inactiveMinutes').set('disabled', ($('setInactiveMinutes').get('disabled') || !$('setInactiveMinutes').get('checked'))); $('save').set('disabled', !isFormValid()); } function isFormValid() { - return !((getSelectedRadioValue('shareLimit') === 'custom') && !$('setRatio').get('checked') && !$('setMinutes').get('checked')); + return !((getSelectedRadioValue('shareLimit') === 'custom') && !$('setRatio').get('checked') + && !$('setTotalMinutes').get('checked') && !$('setInactiveMinutes').get('checked')); } @@ -163,9 +176,14 @@
- - - + + + +
+
+ + +
diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index 8997f756c..e53a0bd8c 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -650,12 +650,21 @@ - + QBT_TR(minutes)QBT_TR[CONTEXT=OptionsDialog] + + + + + + + QBT_TR(minutes)QBT_TR[CONTEXT=OptionsDialog] + + @@ -1700,7 +1709,10 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD const isMaxSeedingTimeEnabled = $('max_seeding_time_checkbox').getProperty('checked'); $('max_seeding_time_value').setProperty('disabled', !isMaxSeedingTimeEnabled); - $('max_ratio_act').setProperty('disabled', !(isMaxRatioEnabled || isMaxSeedingTimeEnabled)); + const isMaxInactiveSeedingTimeEnabled = $('max_inactive_seeding_time_checkbox').getProperty('checked'); + $('max_inactive_seeding_time_value').setProperty('disabled', !isMaxInactiveSeedingTimeEnabled); + + $('max_ratio_act').setProperty('disabled', !(isMaxRatioEnabled || isMaxSeedingTimeEnabled || isMaxInactiveSeedingTimeEnabled)); }; const updateAddTrackersEnabled = function() { @@ -2081,6 +2093,8 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD $('max_ratio_value').setProperty('value', (pref.max_ratio_enabled ? pref.max_ratio : 1)); $('max_seeding_time_checkbox').setProperty('checked', pref.max_seeding_time_enabled); $('max_seeding_time_value').setProperty('value', (pref.max_seeding_time_enabled ? pref.max_seeding_time.toInt() : 1440)); + $('max_inactive_seeding_time_checkbox').setProperty('checked', pref.max_inactive_seeding_time_enabled); + $('max_inactive_seeding_time_value').setProperty('value', (pref.max_inactive_seeding_time_enabled ? pref.max_inactive_seeding_time.toInt() : 1440)); let maxRatioAct = 0; switch (pref.max_ratio_act.toInt()) { case 0: // Pause @@ -2488,6 +2502,18 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD settings.set('max_seeding_time', max_seeding_time); settings.set('max_ratio_act', $('max_ratio_act').getProperty('value').toInt()); + let max_inactive_seeding_time = -1; + if ($('max_inactive_seeding_time_checkbox').getProperty('checked')) { + max_inactive_seeding_time = $('max_inactive_seeding_time_value').getProperty('value').toInt(); + if (isNaN(max_inactive_seeding_time) || (max_inactive_seeding_time < 0) || (max_inactive_seeding_time > 525600)) { + alert("QBT_TR(Seeding time limit must be between 0 and 525600 minutes.)QBT_TR[CONTEXT=HttpServer]"); + return; + } + } + settings.set('max_inactive_seeding_time_enabled', $('max_inactive_seeding_time_checkbox').getProperty('checked')); + settings.set('max_inactive_seeding_time', max_inactive_seeding_time); + settings.set('max_ratio_act', $('max_ratio_act').getProperty('value').toInt()); + // Add trackers settings.set('add_trackers_enabled', $('add_trackers_checkbox').getProperty('checked')); settings.set('add_trackers', $('add_trackers_textarea').getProperty('value'));