Compare commits

...

19 Commits

Author SHA1 Message Date
Vladimir Golovnev
7a9aac79f9 Backport changes to v5.1.x branch
PR #22591.
2025-06-20 19:16:30 +03:00
Vladimir Golovnev
085ae0d1c4 Don't limit the size of read "resume data"
PR #22825.
2025-06-08 18:39:58 +03:00
tehcneko
f748a682ca WebUI: Fix path autofill in set location and new category
Attach `PathAutofill` after `DOMContentLoaded`.
Also removed pathAutofill.js in newfolder.html since it's meant for new RSS folder name.

PR #22773.
2025-05-29 21:37:07 +03:00
Chocobo1
df987cc954 NSIS: revise license page
The GPL doesn't need to be agreed with and therefore remove 'I accept agreement' checkbox and
adjust related UI elements.

Closes #22660.
PR #22775.
2025-05-27 11:22:40 +03:00
bolshoytoster
535fc42747 WebUI: Fix memory leak
See #22734, there is a memory leak in the MooTools .destroy(), this replaces all uses of that with the browser native .remove().

This also overrides the MooTools Document.id function, which is used by $(id). The original function always allocates an ID to elements it selects, the override doesn't, and is also a little more efficient.

Closes #22734.
PR  #22754.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2025-05-26 15:56:37 +03:00
Vladimir Golovnev
1da31bc2e1 RSS: Mark matched article as "read" if refers to duplicate torrent
PR #22477.
2025-05-26 15:47:09 +03:00
Vladimir Golovnev
9515ca59f2 Improve add torrent error handling
PR #22468.
2025-05-26 15:46:17 +03:00
Chocobo1
eaf9017aa4 WebUI: fix wrong replacement sequence
Only IPv6 addresses may have a 'zone index' and therefore it should be replaced last for the
result to be correct.

PR #22724.
2025-05-18 12:41:19 +03:00
Chocobo1
f51ad39ad9 Add fallback for random number generator
`getrandom()` is available since Linux 3.17 (2014/10/05) yet there are older devices that don't
meet this requirement.

Closes #22691.
PR #22723.
2025-05-18 12:40:49 +03:00
Atk
9133b16431 WebUI: Make multi-rename search & replace fields use a monospace font
Before the release of 5.1.0 I believe these two fields both used monospace fonts and also had an increased font size, maybe 14px (I couldn't find in blame where/how this regressed). Ideally, I'd like to bring this back, as it makes elaborate regexes easier to grok. This PR currently just tells the browser to use its monospace font, but I could also add a similar in-line style for font size. I just don't know if this is the best way to solve this problem; if there's a better place for this sort of styling to happen let me know

PR #22719.
2025-05-18 12:40:24 +03:00
KanishkaHalder1771
909a3eb44e Update help message for Windows systems
For windows environment the `--help` output will show :
```
set QBT_NO_SPLASH=1
C:\Program Files\qBittorrent\qbittorrent.exe
```
instead of
```
QBT_NO_SPLASH=1 C:\Program Files\qBittorrent\qbittorrent.exe
```

Fixes #22662.
PR #22695.
2025-05-18 12:39:53 +03:00
Chocobo1
778aa64c54 WebUI: add versioning to local preferences
And provide migration path for changing preferences.

Fixes #22639.
PR #22677.
2025-05-14 10:41:17 +03:00
Vladimir Golovnev
7049f80a01 Fix compilation with Qt 6.6.0
PR #22678.
2025-05-12 12:23:21 +03:00
dezza
87b90b7fd7 WebUI: Remove unselectable from General tab
Making General-tab text `unselectable` is not an improvement.

It begs to add a new `Copy -> Save path` feature, because using `Set location` to copy save path (*which requires a request*) is not faster than simply copying it from the `General` tab by double-left clicking and pressing `CTRL+C`.

I don't see a reason why its necessary to software-restrict people from copying details from the `General`-tab - there are several reasons why you would - incl. the above mentioned usecase for quickly copying save-path, but other than that its counterproductive to limit people from copying the details displayed.

PR #22663.
2025-05-10 11:58:58 +03:00
Vladimir Golovnev
b3690494ab Fix ratio handling
PR #22638.
2025-05-01 21:17:07 +03:00
Vladimir Golovnev
f4e6b515c2 Remove dubious seeding time max value
PR #22624.
2025-05-01 21:16:17 +03:00
Isak05
a721540e6c Fix preview not opening on Wayland
Deferring the opening of the preview slightly gives the preview select
dialog time to close and for focus to shift back to the main window.

PR #22608.
Closes #22607.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
2025-05-01 21:09:45 +03:00
Vladimir Golovnev
3fd05d001f Fix appearance of search history length spinbox
PR #22605.
2025-05-01 21:09:44 +03:00
Vladimir Golovnev
f04b114b64 Don't interpret wildcard pattern as filepath globbing
PR #22590.
Closes #22583.
2025-05-01 21:09:44 +03:00
45 changed files with 353 additions and 119 deletions

View File

@@ -111,7 +111,8 @@ RequestExecutionLevel user
!define MUI_HEADERIMAGE
!define MUI_COMPONENTSPAGE_NODESC
;!define MUI_ICON "qbittorrent.ico"
!define MUI_LICENSEPAGE_CHECKBOX
!define MUI_LICENSEPAGE_BUTTON $(^NextBtn)
!define MUI_LICENSEPAGE_TEXT_BOTTOM "$_CLICK"
!define MUI_LANGDLL_ALLLANGUAGES
;--------------------------------

View File

@@ -920,10 +920,10 @@ int Application::exec()
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
});
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
, [this](const QString &source, const QString &reason)
, [this](const QString &source, const BitTorrent::AddTorrentError &reason)
{
m_desktopIntegration->showNotification(tr("Add torrent failed")
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason.message));
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);

View File

@@ -491,6 +491,12 @@ QString makeUsage(const QString &prgName)
{
const QString indentation {USAGE_INDENTATION, u' '};
#if defined(Q_OS_WIN)
const QString noSplashCommand = u"set QBT_NO_SPLASH=1 && " + prgName;
#else
const QString noSplashCommand = u"QBT_NO_SPLASH=1 " + prgName;
#endif
const QString text = QCoreApplication::translate("CMD Options", "Usage:") + u'\n'
+ indentation + prgName + u' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + u'\n'
@@ -542,7 +548,7 @@ QString makeUsage(const QString &prgName)
"'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
"case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
"'TRUE'. For example, to disable the splash screen: "), 0) + u'\n'
+ u"QBT_NO_SPLASH=1 " + prgName + u'\n'
+ noSplashCommand + u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Command line parameters take precedence over environment variables"), 0) + u'\n';
return text;

View File

@@ -6,6 +6,7 @@ add_library(qbt_base STATIC
applicationcomponent.h
asyncfilestorage.h
bittorrent/abstractfilestorage.h
bittorrent/addtorrenterror.h
bittorrent/addtorrentparams.h
bittorrent/announcetimepoint.h
bittorrent/bandwidthscheduler.h

View File

@@ -140,7 +140,7 @@ void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
}
}
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason)
{
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
{
@@ -154,7 +154,7 @@ void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &in
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
{
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
emit addTorrentFailed(source, reason);
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::Other, reason});
}
void AddTorrentManager::handleDuplicateTorrent(const QString &source
@@ -187,7 +187,7 @@ void AddTorrentManager::handleDuplicateTorrent(const QString &source
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
.arg(source, existingTorrent->name(), message));
emit addTorrentFailed(source, message);
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
}
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)

View File

@@ -35,6 +35,7 @@
#include <QObject>
#include "base/applicationcomponent.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/addtorrentparams.h"
#include "base/torrentfileguard.h"
@@ -66,7 +67,7 @@ public:
signals:
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
void addTorrentFailed(const QString &source, const QString &reason);
void addTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &reason);
protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
@@ -79,7 +80,7 @@ protected:
private:
void onDownloadFinished(const Net::DownloadResult &result);
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason);
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);

View File

@@ -0,0 +1,49 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
*
* 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 <QMetaType>
#include <QString>
namespace BitTorrent
{
struct AddTorrentError
{
enum Kind
{
DuplicateTorrent,
Other
};
Kind kind = Other;
QString message;
};
}
Q_DECLARE_METATYPE(BitTorrent::AddTorrentError)

View File

@@ -147,7 +147,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
const Path torrentFilePath = path() / Path(idString + u".torrent");
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit();
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, torrentSizeLimit);
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, -1);
if (!resumeDataReadResult)
return nonstd::make_unexpected(resumeDataReadResult.error().message);

View File

@@ -34,6 +34,7 @@
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrenterror.h"
#include "addtorrentparams.h"
#include "categoryoptions.h"
#include "sharelimitaction.h"
@@ -481,7 +482,7 @@ namespace BitTorrent
signals:
void startupProgressUpdated(int progress);
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);

View File

@@ -467,9 +467,11 @@ SessionImpl::SessionImpl(QObject *parent)
, m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s))
, m_isAddTrackersFromURLEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersFromURLEnabled"_s), false)
, m_additionalTrackersURL(BITTORRENT_SESSION_KEY(u"AdditionalTrackersURL"_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_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r; })
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s)
, Torrent::NO_SEEDING_TIME_LIMIT, lowerLimited(Torrent::NO_SEEDING_TIME_LIMIT))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s)
, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, lowerLimited(Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT))
, m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
, m_isAddTorrentStopped(BITTORRENT_SESSION_KEY(u"AddTorrentStopped"_s), false)
, m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
@@ -1220,7 +1222,7 @@ qreal SessionImpl::globalMaxRatio() const
void SessionImpl::setGlobalMaxRatio(qreal ratio)
{
if (ratio < 0)
ratio = -1.;
ratio = Torrent::NO_RATIO_LIMIT;
if (ratio != globalMaxRatio())
{
@@ -1236,8 +1238,7 @@ int SessionImpl::globalMaxSeedingMinutes() const
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
{
if (minutes < 0)
minutes = -1;
minutes = std::max(minutes, Torrent::NO_SEEDING_TIME_LIMIT);
if (minutes != globalMaxSeedingMinutes())
{
@@ -1253,7 +1254,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
{
minutes = std::max(minutes, -1);
minutes = std::max(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT);
if (minutes != globalMaxInactiveSeedingMinutes())
{
@@ -2312,19 +2313,19 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
QString description;
if (const qreal ratio = torrent->realRatio();
(ratioLimit >= 0) && (ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
(ratioLimit >= 0) && (ratio >= ratioLimit))
{
reached = true;
description = tr("Torrent reached the share ratio limit.");
}
else if (const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
(seedingTimeLimit >= 0) && (seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
(seedingTimeLimit >= 0) && (seedingTimeInMinutes >= seedingTimeLimit))
{
reached = true;
description = tr("Torrent reached the seeding time limit.");
}
else if (const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
(inactiveSeedingTimeLimit >= 0) && (inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
(inactiveSeedingTimeLimit >= 0) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
{
reached = true;
description = tr("Torrent reached the inactive seeding time limit.");
@@ -2753,7 +2754,10 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
// We should not add the torrent if it is already
// processed or is pending to add to session
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
{
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, tr("Duplicate torrent")});
return false;
}
if (Torrent *torrent = findTorrent(infoHash))
{
@@ -2767,16 +2771,20 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
if (!isMergeTrackersEnabled())
{
const QString message = tr("Merging of trackers is disabled");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Merging of trackers is disabled")));
.arg(torrent->name(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
return false;
}
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
if (isPrivate)
{
const QString message = tr("Trackers cannot be merged because it is a private torrent");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers cannot be merged because it is a private torrent")));
.arg(torrent->name(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
return false;
}
@@ -2784,8 +2792,10 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
torrent->addTrackers(source.trackers());
torrent->addUrlSeeds(source.urlSeeds());
const QString message = tr("Trackers are merged from new source");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers are merged from new source")));
.arg(torrent->name(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
return false;
}
@@ -5707,7 +5717,9 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.end())
{
emit addTorrentFailed(infoHash, msg);
const AddTorrentError::Kind errorKind = (alert->error == lt::errors::duplicate_torrent)
? AddTorrentError::DuplicateTorrent : AddTorrentError::Other;
emit addTorrentFailed(infoHash, {errorKind, msg});
m_loadingTorrents.erase(loadingTorrentsIter);
}
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))

View File

@@ -29,6 +29,8 @@
#include "torrent.h"
#include <limits>
#include <QHash>
#include "infohash.h"
@@ -51,9 +53,7 @@ namespace BitTorrent
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;
const qreal Torrent::MAX_RATIO = std::numeric_limits<qreal>::infinity();
TorrentID Torrent::id() const
{

View File

@@ -132,8 +132,6 @@ namespace BitTorrent
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;

View File

@@ -1549,7 +1549,8 @@ qreal TorrentImpl::realRatio() const
const qreal ratio = upload / static_cast<qreal>(download);
Q_ASSERT(ratio >= 0);
return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
return ratio;
}
int TorrentImpl::uploadPayloadRate() const
@@ -2712,8 +2713,6 @@ void TorrentImpl::setRatioLimit(qreal limit)
{
if (limit < USE_GLOBAL_RATIO)
limit = NO_RATIO_LIMIT;
else if (limit > MAX_RATIO)
limit = MAX_RATIO;
if (m_ratioLimit != limit)
{
@@ -2727,8 +2726,6 @@ void TorrentImpl::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)
{
@@ -2742,8 +2739,6 @@ 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)
{

View File

@@ -375,10 +375,24 @@ void AutoDownloader::handleTorrentAdded(const QString &source)
}
}
void AutoDownloader::handleAddTorrentFailed(const QString &source)
void AutoDownloader::handleAddTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &error)
{
m_waitingJobs.remove(source);
// TODO: Re-schedule job here.
const auto job = m_waitingJobs.take(source);
if (!job)
return;
if (error.kind == BitTorrent::AddTorrentError::DuplicateTorrent)
{
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
{
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
article->markAsRead();
}
}
else
{
// TODO: Re-schedule job here.
}
}
void AutoDownloader::handleNewArticle(const Article *article)

View File

@@ -37,6 +37,7 @@
#include <QSharedPointer>
#include "base/applicationcomponent.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/exceptions.h"
#include "base/settingvalue.h"
#include "base/utils/thread.h"
@@ -111,7 +112,7 @@ namespace RSS
private slots:
void process();
void handleTorrentAdded(const QString &source);
void handleAddTorrentFailed(const QString &url);
void handleAddTorrentFailed(const QString &url, const BitTorrent::AddTorrentError &error);
void handleNewArticle(const Article *article);
void handleFeedURLChanged(Feed *feed, const QString &oldURL);

View File

@@ -42,7 +42,7 @@
uint32_t Utils::Random::rand(const uint32_t min, const uint32_t max)
{
static RandomLayer layer;
static const RandomLayer layer;
// new distribution is cheap: https://stackoverflow.com/a/19036349
std::uniform_int_distribution<uint32_t> uniform(min, max);

View File

@@ -27,6 +27,7 @@
*/
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <limits>
@@ -44,6 +45,27 @@ namespace
RandomLayer()
{
if (::getrandom(nullptr, 0, 0) < 0)
{
if (errno == ENOSYS)
{
// underlying kernel does not implement this system call
// fallback to `urandom`
m_randDev = fopen("/dev/urandom", "rb");
if (!m_randDev)
qFatal("Failed to open /dev/urandom. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
else
{
qFatal("getrandom() error. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
}
}
~RandomLayer()
{
if (m_randDev)
fclose(m_randDev);
}
static constexpr result_type min()
@@ -56,7 +78,15 @@ namespace
return std::numeric_limits<result_type>::max();
}
result_type operator()()
result_type operator()() const
{
if (!m_randDev)
return getRandomViaAPI();
return getRandomViaFile();
}
private:
result_type getRandomViaAPI() const
{
const int RETRY_MAX = 3;
@@ -68,10 +98,21 @@ namespace
return buf;
if (result < 0)
qFatal("getrandom() error. Reason: %s. Error code: %d.", std::strerror(errno), errno);
qFatal("getrandom() error. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
qFatal("getrandom() failed. Reason: too many retries.");
}
result_type getRandomViaFile() const
{
result_type buf = 0;
if (fread(&buf, sizeof(buf), 1, m_randDev) == 1)
return buf;
qFatal("Read /dev/urandom error. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
FILE *m_randDev = nullptr;
};
}

View File

@@ -46,7 +46,7 @@ namespace
: m_randDev {fopen("/dev/urandom", "rb")}
{
if (!m_randDev)
qFatal("Failed to open /dev/urandom. Reason: %s. Error code: %d.", std::strerror(errno), errno);
qFatal("Failed to open /dev/urandom. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
~RandomLayer()
@@ -67,10 +67,10 @@ namespace
result_type operator()() const
{
result_type buf = 0;
if (fread(&buf, sizeof(buf), 1, m_randDev) != 1)
qFatal("Read /dev/urandom error. Reason: %s. Error code: %d.", std::strerror(errno), errno);
if (fread(&buf, sizeof(buf), 1, m_randDev) == 1)
return buf;
return buf;
qFatal("Read /dev/urandom error. Reason: \"%s\". Error code: %d.", std::strerror(errno), errno);
}
private:

View File

@@ -60,7 +60,7 @@ namespace
return std::numeric_limits<result_type>::max();
}
result_type operator()()
result_type operator()() const
{
result_type buf = 0;
const bool result = m_processPrng(reinterpret_cast<PBYTE>(&buf), sizeof(buf));

View File

@@ -61,7 +61,12 @@ QString Utils::String::fromLocal8Bit(const std::string_view string)
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 1))
return QRegularExpression::wildcardToRegularExpression(pattern
, (QRegularExpression::UnanchoredWildcardConversion | QRegularExpression::NonPathWildcardConversion));
#else
return QRegularExpression::wildcardToRegularExpression(pattern, QRegularExpression::UnanchoredWildcardConversion);
#endif
}
QStringList Utils::String::splitCommand(const QString &command)

View File

@@ -3021,9 +3021,6 @@ Disable encryption: Only connect to peers without protocol encryption</string>
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximum">
<double>9998.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
@@ -3283,15 +3280,9 @@ Disable encryption: Only connect to peers without protocol encryption</string>
</item>
<item>
<widget class="QSpinBox" name="searchHistoryLengthSpinBox">
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
</property>
<property name="maximum">
<number>99</number>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::DefaultStepType</enum>
</property>
</widget>
</item>
<item>

View File

@@ -439,10 +439,10 @@ void PropertiesWidget::loadDynamicData()
// Update ratio info
const qreal ratio = m_torrent->realRatio();
m_ui->labelShareRatioVal->setText(ratio > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(ratio, 2));
m_ui->labelShareRatioVal->setText(ratio >= BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(ratio, 2));
const qreal popularity = m_torrent->popularity();
m_ui->labelPopularityVal->setText(popularity > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(popularity, 2));
m_ui->labelPopularityVal->setText(popularity >= BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(popularity, 2));
m_ui->labelSeedsVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
.arg(QString::number(m_torrent->seedsCount())

View File

@@ -47,9 +47,6 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximum">
<double>9998.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>

View File

@@ -293,7 +293,7 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
if (hideValues && (value <= 0))
return {};
return ((static_cast<int>(value) == -1) || (value > BitTorrent::Torrent::MAX_RATIO))
return ((static_cast<int>(value) == -1) || (value >= BitTorrent::Torrent::MAX_RATIO))
? C_INFINITY : Utils::String::fromDouble(value, 2);
};

View File

@@ -311,10 +311,7 @@ void TransferListWidget::torrentDoubleClicked()
case PREVIEW_FILE:
if (torrentContainsPreviewableFiles(torrent))
{
auto *dialog = new PreviewSelectDialog(this, torrent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &PreviewSelectDialog::readyToPreviewFile, this, &TransferListWidget::previewFile);
dialog->show();
openPreviewSelectDialog(torrent);
}
else
{
@@ -616,10 +613,7 @@ void TransferListWidget::previewSelectedTorrents()
{
if (torrentContainsPreviewableFiles(torrent))
{
auto *dialog = new PreviewSelectDialog(this, torrent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &PreviewSelectDialog::readyToPreviewFile, this, &TransferListWidget::previewFile);
dialog->show();
openPreviewSelectDialog(torrent);
}
else
{
@@ -1448,3 +1442,13 @@ void TransferListWidget::wheelEvent(QWheelEvent *event)
QTreeView::wheelEvent(event); // event delegated to base class
}
void TransferListWidget::openPreviewSelectDialog(const BitTorrent::Torrent *torrent)
{
auto *dialog = new PreviewSelectDialog(this, torrent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
// Qt::QueuedConnection is required to prevent a bug on wayland compositors where the preview won't open.
// It occurs when the window focus shifts immediately after TransferListWidget::previewFile has been called.
connect(dialog, &PreviewSelectDialog::readyToPreviewFile, this, &TransferListWidget::previewFile, Qt::QueuedConnection);
dialog->show();
}

View File

@@ -123,6 +123,7 @@ private:
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void openPreviewSelectDialog(const BitTorrent::Torrent *torrent);
QModelIndex mapToSource(const QModelIndex &index) const;
QModelIndexList mapToSource(const QModelIndexList &indexes) const;
QModelIndex mapFromSource(const QModelIndex &index) const;

View File

@@ -96,7 +96,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
const auto adjustRatio = [](const qreal ratio) -> qreal
{
return (ratio > BitTorrent::Torrent::MAX_RATIO) ? -1 : ratio;
return (ratio >= BitTorrent::Torrent::MAX_RATIO) ? -1 : ratio;
};
const auto getLastActivityTime = [&torrent]() -> qlonglong

View File

@@ -522,8 +522,8 @@ void TorrentsController::propertiesAction()
{KEY_PROP_SEEDS_TOTAL, torrent->totalSeedsCount()},
{KEY_PROP_PEERS, torrent->leechsCount()},
{KEY_PROP_PEERS_TOTAL, torrent->totalLeechersCount()},
{KEY_PROP_RATIO, ((ratio > BitTorrent::Torrent::MAX_RATIO) ? -1 : ratio)},
{KEY_PROP_POPULARITY, ((popularity > BitTorrent::Torrent::MAX_RATIO) ? -1 : popularity)},
{KEY_PROP_RATIO, ((ratio >= BitTorrent::Torrent::MAX_RATIO) ? -1 : ratio)},
{KEY_PROP_POPULARITY, ((popularity >= BitTorrent::Torrent::MAX_RATIO) ? -1 : popularity)},
{KEY_PROP_REANNOUNCE, torrent->nextAnnounce()},
{KEY_PROP_TOTAL_SIZE, torrent->totalSize()},
{KEY_PROP_PIECES_NUM, torrent->piecesCount()},

View File

@@ -26,6 +26,7 @@
<script defer src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
<script defer src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
<script defer src="scripts/lib/mocha.min.js"></script>
<script defer src="scripts/monkeypatch.js?v=${CACHEID}"></script>
<script defer src="scripts/cache.js?v=${CACHEID}"></script>
<script defer src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script defer src="scripts/color-scheme.js?v=${CACHEID}"></script>

View File

@@ -147,9 +147,9 @@
break;
}
});
});
window.qBittorrent.pathAutofill.attachPathAutofill();
window.qBittorrent.pathAutofill.attachPathAutofill();
});
</script>
</head>

View File

@@ -10,7 +10,6 @@
<script src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script src="scripts/color-scheme.js?v=${CACHEID}"></script>
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script src="scripts/pathAutofill.js?v=${CACHEID}"></script>
<script>
"use strict";
@@ -62,15 +61,13 @@
});
});
});
window.qBittorrent.pathAutofill.attachPathAutofill();
</script>
</head>
<body>
<div style="padding: 10px 10px 0px 10px;">
<label for="folderName" style="font-weight: bold;">QBT_TR(Folder name:)QBT_TR[CONTEXT=RSSWidget]</label>
<input type="text" id="folderName" class="pathDirectory" style="width: 320px;">
<input type="text" id="folderName" style="width: 320px;">
<div style="text-align: center; padding-top: 10px;">
<input type="button" value="QBT_TR(OK)QBT_TR[CONTEXT=HttpServer]" id="submitButton">
</div>

View File

@@ -411,7 +411,7 @@
<label for="multirename_rememberprefs_checkbox">QBT_TR(Remember Multi-Rename settings)QBT_TR[CONTEXT=OptionsDialog]</label>
</div>
<hr>
<textarea id="multiRenameSearch" placeholder="QBT_TR(Search Files)QBT_TR[CONTEXT=PropertiesWidget]" aria-label="QBT_TR(Search Files)QBT_TR[CONTEXT=PropertiesWidget]" style="width: calc(100% - 8px); resize: vertical; min-height: 30px;"></textarea>
<textarea id="multiRenameSearch" placeholder="QBT_TR(Search Files)QBT_TR[CONTEXT=PropertiesWidget]" aria-label="QBT_TR(Search Files)QBT_TR[CONTEXT=PropertiesWidget]" style="width: calc(100% - 8px); resize: vertical; min-height: 30px; font-family: monospace;"></textarea>
<div class="formRow">
<input type="checkbox" id="use_regex_search">
<label for="use_regex_search">QBT_TR(Use regular expressions)QBT_TR[CONTEXT=PropertiesWidget]</label>
@@ -425,7 +425,7 @@
<label for="case_sensitive">QBT_TR(Case sensitive)QBT_TR[CONTEXT=PropertiesWidget]</label>
</div>
<hr>
<textarea id="multiRenameReplace" placeholder="QBT_TR(Replacement Input)QBT_TR[CONTEXT=PropertiesWidget]" aria-label="QBT_TR(Replacement Input)QBT_TR[CONTEXT=PropertiesWidget]" style="width: calc(100% - 8px); resize: vertical; min-height: 30px;"></textarea>
<textarea id="multiRenameReplace" placeholder="QBT_TR(Replacement Input)QBT_TR[CONTEXT=PropertiesWidget]" aria-label="QBT_TR(Replacement Input)QBT_TR[CONTEXT=PropertiesWidget]" style="width: calc(100% - 8px); resize: vertical; min-height: 30px; font-family: monospace;"></textarea>
<select id="applies_to_option" name="applies_to_option" aria-label="QBT_TR(Apply to which filename part)QBT_TR[CONTEXT=PropertiesWidget]" style="width: 100%; margin-bottom: 5px;">
<option selected value="FilenameExtension">QBT_TR(Filename + Extension)QBT_TR[CONTEXT=PropertiesWidget]</option>
<option value="Filename">QBT_TR(Filename)QBT_TR[CONTEXT=PropertiesWidget]</option>

View File

@@ -174,7 +174,9 @@ let selectedStatus = LocalPreferences.get("selected_filter", "all");
let setStatusFilter = () => {};
let toggleFilterDisplay = () => {};
window.addEventListener("DOMContentLoaded", () => {
window.addEventListener("DOMContentLoaded", (event) => {
window.qBittorrent.LocalPreferences.upgrade();
let isSearchPanelLoaded = false;
let isLogPanelLoaded = false;
let isRssPanelLoaded = false;
@@ -497,7 +499,7 @@ window.addEventListener("DOMContentLoaded", () => {
if (!categoryList)
return;
[...categoryList.children].forEach((el) => { el.destroy(); });
[...categoryList.children].forEach((el) => { el.remove(); });
const categoryItemTemplate = document.getElementById("categoryFilterItem");
@@ -618,7 +620,7 @@ window.addEventListener("DOMContentLoaded", () => {
if (tagFilterList === null)
return;
[...tagFilterList.children].forEach((el) => { el.destroy(); });
[...tagFilterList.children].forEach((el) => { el.remove(); });
const tagItemTemplate = document.getElementById("tagFilterItem");
@@ -671,7 +673,7 @@ window.addEventListener("DOMContentLoaded", () => {
if (trackerFilterList === null)
return;
[...trackerFilterList.children].forEach((el) => { el.destroy(); });
[...trackerFilterList.children].forEach((el) => { el.remove(); });
const trackerItemTemplate = document.getElementById("trackerFilterItem");
@@ -989,9 +991,9 @@ window.addEventListener("DOMContentLoaded", () => {
lastExternalAddressLabel = "QBT_TR(External IPs: %1, %2)QBT_TR[CONTEXT=HttpServer]";
else if (hasIPv4Address || hasIPv6Address)
lastExternalAddressLabel = "QBT_TR(External IP: %1%2)QBT_TR[CONTEXT=HttpServer]";
// replace in reverse order ('%2' before '%1') in case address contains a % character.
// for example, see https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses_(with_zone_index)
externalIPsElement.textContent = lastExternalAddressLabel.replace("%2", lastExternalAddressV6).replace("%1", lastExternalAddressV4);
// https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses_(with_zone_index)
lastExternalAddressLabel = lastExternalAddressLabel.replace("%1", lastExternalAddressV4).replace("%2", lastExternalAddressV6);
externalIPsElement.textContent = lastExternalAddressLabel;
externalIPsElement.classList.remove("invisible");
externalIPsElement.previousElementSibling.classList.remove("invisible");
}

View File

@@ -478,7 +478,7 @@ window.qBittorrent.ContextMenu ??= (() => {
updateCategoriesSubMenu(categories) {
const contextCategoryList = $("contextCategoryList");
[...contextCategoryList.children].forEach((el) => { el.destroy(); });
[...contextCategoryList.children].forEach((el) => { el.remove(); });
const createMenuItem = (text, imgURL, clickFn) => {
const anchor = document.createElement("a");

View File

@@ -907,14 +907,14 @@ window.qBittorrent.DynamicTable ??= (() => {
this.selectedRows.erase(rowId);
this.rows.delete(rowId);
const tr = this.getTrByRowId(rowId);
tr?.destroy();
tr?.remove();
},
clear: function() {
this.deselectAll();
this.rows.clear();
for (const tr of this.getTrs())
tr.destroy();
tr.remove();
},
selectedRowsIds: function() {

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Mike Tzou (Chocobo1)
* Copyright (C) 2019 Thomas Piccirello <thomas.piccirello@gmail.com>
*
* This program is free software; you can redistribute it and/or
@@ -32,7 +33,8 @@ window.qBittorrent ??= {};
window.qBittorrent.LocalPreferences ??= (() => {
const exports = () => {
return {
LocalPreferences: LocalPreferences
LocalPreferences: LocalPreferences,
upgrade: upgrade
};
};
@@ -53,6 +55,10 @@ window.qBittorrent.LocalPreferences ??= (() => {
}
}
size() {
return localStorage.length;
}
remove(key) {
try {
localStorage.removeItem(key);
@@ -63,6 +69,40 @@ window.qBittorrent.LocalPreferences ??= (() => {
}
};
const localPreferences = new LocalPreferences();
const upgrade = () => {
const MIGRATION_VERSION = 1;
const MIGRATION_VERSION_KEY = "MigrationVersion";
// clean start
if (localPreferences.size() === 0) {
localPreferences.set(MIGRATION_VERSION_KEY, MIGRATION_VERSION);
return;
}
// already in use
const version = Number(localPreferences.get(MIGRATION_VERSION_KEY)); // `0` on first initialization
if (version !== MIGRATION_VERSION) {
if (version < 1)
resetSideFilters();
localPreferences.set(MIGRATION_VERSION_KEY, MIGRATION_VERSION);
}
};
const resetSideFilters = () => {
// conditionally reset the filter to default to avoid none selected
const clear = (key) => {
const value = Number(localPreferences.get(key));
if ((value === 1) || (value === 2)) // affected values
localPreferences.remove(key);
};
clear("selected_category");
clear("selected_tag");
clear("selected_tracker");
};
return exports();
})();
Object.freeze(window.qBittorrent.LocalPreferences);

View File

@@ -0,0 +1,72 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 bolshoytoster <toasterbig@gmail.com>
* Copyright (C) 2025 Mike Tzou (Chocobo1)
*
* 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.
*/
"use strict";
window.qBittorrent ??= {};
window.qBittorrent.MonkeyPatch ??= (() => {
const exports = () => {
return {
patch: patch
};
};
const patch = () => {
patchMootoolsDocumentId();
};
const patchMootoolsDocumentId = () => {
// Override MooTools' `document.id` (used for `$(id)`), which prevents it
// from allocating a `uniqueNumber` for elements that don't need it.
// MooTools and MochaUI use it internally.
if (document.id === undefined)
return;
document.id = (el) => {
if ((el === null) || (el === undefined))
return null;
switch (typeof el) {
case "object":
return el;
case "string":
return document.getElementById(el);
}
return null;
};
};
return exports();
})();
Object.freeze(window.qBittorrent.MonkeyPatch);
// execute now
window.qBittorrent.MonkeyPatch.patch();

View File

@@ -248,7 +248,7 @@ window.qBittorrent.Search ??= (() => {
if (state && state.running)
stopSearch(searchId);
tab.destroy();
tab.remove();
fetch("api/v2/search/delete", {
method: "POST",

View File

@@ -10,6 +10,7 @@
<script src="scripts/localpreferences.js?v=${CACHEID}"></script>
<script src="scripts/color-scheme.js?v=${CACHEID}"></script>
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
<script src="scripts/pathAutofill.js?v=${CACHEID}"></script>
<script>
"use strict";
@@ -62,6 +63,8 @@
window.parent.qBittorrent.Client.closeFrameWindow(window);
});
});
window.qBittorrent.pathAutofill.attachPathAutofill();
});
</script>
</head>
@@ -69,7 +72,7 @@
<body>
<div style="padding: 10px 10px 0px 10px;">
<label for="setLocation" style="font-weight: bold;">QBT_TR(Location:)QBT_TR[CONTEXT=TransferListWidget]</label>
<input type="text" id="setLocation" autocorrect="off" autocapitalize="none" style="width: 99%;">
<input type="text" id="setLocation" class="pathDirectory" autocorrect="off" autocapitalize="none" style="width: 99%;">
<div style="float: none; width: 99%;" id="error_div">&nbsp;</div>
<div style="text-align: center; padding-top: 10px;">
<input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" id="setLocationButton">

View File

@@ -172,17 +172,17 @@
<div style="margin-left: 40px; margin-bottom: 5px;">
<input type="checkbox" id="setRatio" class="shareLimitInput" onclick="enableInputBoxes()">
<label id="ratioLabel" for="setRatio">QBT_TR(ratio)QBT_TR[CONTEXT=UpDownRatioDialog]</label>
<input type="number" id="ratio" value="0.00" step=".01" min="0" max="9999" class="shareLimitInput" aria-labelledby="ratioLabel">
<input type="number" id="ratio" value="0.00" step=".01" min="0" class="shareLimitInput" aria-labelledby="ratioLabel">
</div>
<div style="margin-left: 40px; margin-bottom: 5px;">
<input type="checkbox" id="setTotalMinutes" class="shareLimitInput" onclick="enableInputBoxes()">
<label id="totalMinutesLabel" for="setTotalMinutes">QBT_TR(total minutes)QBT_TR[CONTEXT=UpDownRatioDialog]</label>
<input type="number" id="totalMinutes" value="0" step="1" min="0" max="525600" class="shareLimitInput" aria-labelledby="totalMinutesLabel">
<input type="number" id="totalMinutes" value="0" step="1" min="0" class="shareLimitInput" aria-labelledby="totalMinutesLabel">
</div>
<div style="margin-left: 40px; margin-bottom: 5px;">
<input type="checkbox" id="setInactiveMinutes" class="shareLimitInput" onclick="enableInputBoxes()">
<label id="inactiveMinutesLabel" for="setInactiveMinutes">QBT_TR(inactive minutes)QBT_TR[CONTEXT=UpDownRatioDialog]</label>
<input type="number" id="inactiveMinutes" value="0" step="1" min="0" max="525600" class="shareLimitInput" aria-labelledby="inactiveMinutesLabel">
<input type="number" id="inactiveMinutes" value="0" step="1" min="0" class="shareLimitInput" aria-labelledby="inactiveMinutesLabel">
</div>
<div style="text-align: center; padding-top: 10px;">
<input type="button" value="QBT_TR(Save)QBT_TR[CONTEXT=HttpServer]" id="save">

View File

@@ -104,7 +104,7 @@
};
const deleteCookie = (element) => {
element.closest("tr").destroy();
element.closest("tr").remove();
};
const save = () => {

View File

@@ -2110,7 +2110,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
// Advanced Tab
const updateNetworkInterfaces = (default_iface, default_iface_name) => {
[...document.getElementById("networkInterface").children].forEach((el) => { el.destroy(); });
[...document.getElementById("networkInterface").children].forEach((el) => { el.remove(); });
fetch("api/v2/app/networkInterfaceList", {
method: "GET",
@@ -2139,7 +2139,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
};
const updateInterfaceAddresses = (iface, default_addr) => {
[...document.getElementById("optionalIPAddressToBind").children].forEach((el) => { el.destroy(); });
[...document.getElementById("optionalIPAddressToBind").children].forEach((el) => { el.remove(); });
const url = new URL("api/v2/app/networkInterfaceAddressList", window.location);
url.search = new URLSearchParams({
@@ -2876,22 +2876,22 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
}
// Share Ratio Limiting
let max_ratio = -1;
if ($("max_ratio_checkbox").checked) {
max_ratio = Number($("max_ratio_value").value);
if (isNaN(max_ratio) || (max_ratio < 0) || (max_ratio > 9998)) {
alert("QBT_TR(Share ratio limit must be between 0 and 9998.)QBT_TR[CONTEXT=HttpServer]");
let maxRatio = -1;
if (document.getElementById("maxRatioCheckbox").checked) {
maxRatio = Number(document.getElementById("maxRatioValue").value);
if (isNaN(maxRatio) || (maxRatio < 0)) {
alert("QBT_TR(Share ratio limit must not have a negative value.)QBT_TR[CONTEXT=HttpServer]");
return;
}
}
settings["max_ratio_enabled"] = $("max_ratio_checkbox").checked;
settings["max_ratio"] = max_ratio;
let max_seeding_time = -1;
if ($("max_seeding_time_checkbox").checked) {
max_seeding_time = Number($("max_seeding_time_value").value);
if (isNaN(max_seeding_time) || (max_seeding_time < 0) || (max_seeding_time > 525600)) {
alert("QBT_TR(Seeding time limit must be between 0 and 525600 minutes.)QBT_TR[CONTEXT=HttpServer]");
let maxSeedingTime = -1;
if (document.getElementById("maxSeedingTimeCheckbox").checked) {
maxSeedingTime = Number(document.getElementById("maxSeedingTimeValue").value);
if (Number.isNaN(maxSeedingTime) || (maxSeedingTime < 0)) {
alert("QBT_TR(Seeding time limit must not have a negative value.)QBT_TR[CONTEXT=HttpServer]");
return;
}
}
@@ -2899,11 +2899,11 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
settings["max_seeding_time"] = max_seeding_time;
settings["max_ratio_act"] = Number($("max_ratio_act").value);
let max_inactive_seeding_time = -1;
if ($("max_inactive_seeding_time_checkbox").checked) {
max_inactive_seeding_time = Number($("max_inactive_seeding_time_value").value);
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]");
let maxInactiveSeedingTime = -1;
if (document.getElementById("maxInactiveSeedingTimeCheckbox").checked) {
maxInactiveSeedingTime = Number(document.getElementById("maxInactiveSeedingTimeValue").value);
if (Number.isNaN(maxInactiveSeedingTime) || (maxInactiveSeedingTime < 0)) {
alert("QBT_TR(Seeding time limit must not have a negative value.)QBT_TR[CONTEXT=HttpServer]");
return;
}
}

View File

@@ -1,4 +1,4 @@
<div id="propGeneral" class="propertiesTabContent invisible unselectable">
<div id="propGeneral" class="propertiesTabContent invisible">
<div id="propProgressWrapper">
<span>QBT_TR(Progress:)QBT_TR[CONTEXT=PropertiesWidget]</span>
<span id="progress"></span>

View File

@@ -422,7 +422,7 @@
};
const clearDetails = () => {
[...document.getElementById("rssDetailsView").children].forEach((el) => { el.destroy(); });
[...document.getElementById("rssDetailsView").children].forEach((el) => { el.remove(); });
};
const showDetails = (feedUid, articleID) => {

View File

@@ -407,6 +407,7 @@
<file>private/scripts/localpreferences.js</file>
<file>private/scripts/misc.js</file>
<file>private/scripts/mocha-init.js</file>
<file>private/scripts/monkeypatch.js</file>
<file>private/scripts/pathAutofill.js</file>
<file>private/scripts/piecesbar.js</file>
<file>private/scripts/progressbar.js</file>