mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-19 21:03:30 -07:00
Merge branch 'master' into multi-file-export
This commit is contained in:
commit
c50e0b886b
108 changed files with 2842 additions and 2802 deletions
10
.github/workflows/ci_file_health.yaml
vendored
10
.github/workflows/ci_file_health.yaml
vendored
|
@ -52,13 +52,13 @@ jobs:
|
|||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
pip install zizmor
|
||||
IGNORE_RULEID='(.ruleId != "template-injection")
|
||||
and (.ruleId != "unpinned-uses")'
|
||||
IGNORE_ID='(.id != "template-injection")
|
||||
and (.id != "unpinned-uses")'
|
||||
IGNORE_RULEID='(.ruleId != "zizmor/template-injection")
|
||||
and (.ruleId != "zizmor/unpinned-uses")'
|
||||
IGNORE_ID='(.id != "zizmor/template-injection")
|
||||
and (.id != "zizmor/unpinned-uses")'
|
||||
zizmor \
|
||||
--format sarif \
|
||||
--pedantic \
|
||||
--persona auditor \
|
||||
./ \
|
||||
| jq "(.runs[].results |= map(select($IGNORE_RULEID)))
|
||||
| (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \
|
||||
|
|
3
.github/workflows/ci_ubuntu.yaml
vendored
3
.github/workflows/ci_ubuntu.yaml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.5.2"]
|
||||
qt_version: ["6.6.3"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
|
@ -162,6 +162,7 @@ jobs:
|
|||
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
rm -f "${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/sqldrivers/libqsqlmimer.so"
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
rm qbittorrent/apprun-hooks/*
|
||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||
|
|
2
.github/workflows/coverity-scan.yaml
vendored
2
.github/workflows/coverity-scan.yaml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
matrix:
|
||||
libt_version: ["2.0.11"]
|
||||
qbt_gui: ["GUI=ON"]
|
||||
qt_version: ["6.5.2"]
|
||||
qt_version: ["6.6.3"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
|
|
|
@ -69,7 +69,7 @@ repos:
|
|||
- ts
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell.git
|
||||
rev: v2.4.0
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell
|
||||
name: Check spelling (codespell)
|
||||
|
@ -88,7 +88,7 @@ repos:
|
|||
- ts
|
||||
|
||||
- repo: https://github.com/crate-ci/typos.git
|
||||
rev: v1.29.4
|
||||
rev: v1.32.0
|
||||
hooks:
|
||||
- id: typos
|
||||
name: Check spelling (typos)
|
||||
|
|
|
@ -8,7 +8,7 @@ project(qBittorrent
|
|||
|
||||
# version requirements - older versions may work, but you are on your own
|
||||
set(minBoostVersion 1.76)
|
||||
set(minQt6Version 6.5.0)
|
||||
set(minQt6Version 6.6.0)
|
||||
set(minOpenSSLVersion 3.0.2)
|
||||
set(minLibtorrent1Version 1.2.19)
|
||||
set(minLibtorrentVersion 2.0.10)
|
||||
|
|
2
INSTALL
2
INSTALL
|
@ -11,7 +11,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
|||
|
||||
- OpenSSL >= 3.0.2
|
||||
|
||||
- Qt 6.5.0 - 6.x
|
||||
- Qt 6.6.0 - 6.x
|
||||
|
||||
- zlib >= 1.2.11
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ target_compile_features(qbt_common_cfg INTERFACE
|
|||
)
|
||||
|
||||
target_compile_definitions(qbt_common_cfg INTERFACE
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060500
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060600
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
QT_NO_CAST_TO_ASCII
|
||||
QT_NO_CAST_FROM_BYTEARRAY
|
||||
|
|
|
@ -410,7 +410,7 @@ void Application::setMemoryWorkingSetLimit(const int size)
|
|||
return;
|
||||
|
||||
m_storeMemoryWorkingSetLimit = size;
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
}
|
||||
|
@ -674,9 +674,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
|||
QProcess proc;
|
||||
proc.setProgram(command);
|
||||
proc.setArguments(args);
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
if (proc.startDetached())
|
||||
{
|
||||
|
@ -846,7 +844,7 @@ int Application::exec()
|
|||
printf("%s\n", qUtf8Printable(loadingStr));
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
|
||||
|
@ -1204,7 +1202,7 @@ void Application::shutdownCleanup([[maybe_unused]] QSessionManager &manager)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
void Application::applyMemoryWorkingSetLimit() const
|
||||
{
|
||||
const size_t MiB = 1024 * 1024;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020-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
|
||||
|
@ -27,13 +27,14 @@
|
|||
*/
|
||||
|
||||
#include "filesearcher.h"
|
||||
#include "base/bittorrent/common.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
|
||||
void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
|
||||
, const Path &savePath, const Path &downloadPath, const bool forceAppendExt)
|
||||
#include <QPromise>
|
||||
|
||||
#include "base/bittorrent/common.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const auto findInDir = [](const Path &dirPath, PathList &fileNames, const bool forceAppendExt) -> bool
|
||||
bool findInDir(const Path &dirPath, PathList &fileNames, const bool forceAppendExt)
|
||||
{
|
||||
bool found = false;
|
||||
for (Path &fileName : fileNames)
|
||||
|
@ -58,8 +59,12 @@ void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &origi
|
|||
}
|
||||
|
||||
return found;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearcher::search(const PathList &originalFileNames, const Path &savePath
|
||||
, const Path &downloadPath, const bool forceAppendExt, QPromise<FileSearchResult> &promise)
|
||||
{
|
||||
Path usedPath = savePath;
|
||||
PathList adjustedFileNames = originalFileNames;
|
||||
const bool found = findInDir(usedPath, adjustedFileNames, (forceAppendExt && downloadPath.isEmpty()));
|
||||
|
@ -69,5 +74,5 @@ void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &origi
|
|||
findInDir(usedPath, adjustedFileNames, forceAppendExt);
|
||||
}
|
||||
|
||||
emit searchFinished(id, usedPath, adjustedFileNames);
|
||||
promise.addResult(FileSearchResult {.savePath = usedPath, .fileNames = adjustedFileNames});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020-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
|
||||
|
@ -32,10 +32,13 @@
|
|||
|
||||
#include "base/path.h"
|
||||
|
||||
namespace BitTorrent
|
||||
template <typename T> class QPromise;
|
||||
|
||||
struct FileSearchResult
|
||||
{
|
||||
class TorrentID;
|
||||
}
|
||||
Path savePath;
|
||||
PathList fileNames;
|
||||
};
|
||||
|
||||
class FileSearcher final : public QObject
|
||||
{
|
||||
|
@ -43,12 +46,8 @@ class FileSearcher final : public QObject
|
|||
Q_DISABLE_COPY_MOVE(FileSearcher)
|
||||
|
||||
public:
|
||||
FileSearcher() = default;
|
||||
using QObject::QObject;
|
||||
|
||||
public slots:
|
||||
void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
|
||||
, const Path &savePath, const Path &downloadPath, bool forceAppendExt);
|
||||
|
||||
signals:
|
||||
void searchFinished(const BitTorrent::TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
void search(const PathList &originalFileNames, const Path &savePath
|
||||
, const Path &downloadPath, bool forceAppendExt, QPromise<FileSearchResult> &promise);
|
||||
};
|
||||
|
|
|
@ -493,7 +493,6 @@ namespace BitTorrent
|
|||
void categoryOptionsChanged(const QString &categoryName);
|
||||
void fullDiskError(Torrent *torrent, const QString &msg);
|
||||
void IPFilterParsed(bool error, int ruleCount);
|
||||
void loadTorrentFailed(const QString &error);
|
||||
void metadataDownloaded(const TorrentInfo &info);
|
||||
void restored();
|
||||
void paused();
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "sessionimpl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <queue>
|
||||
|
@ -61,6 +62,7 @@
|
|||
#include <QDeadlineTimer>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFuture>
|
||||
#include <QHostAddress>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
@ -69,6 +71,7 @@
|
|||
#include <QMutexLocker>
|
||||
#include <QNetworkAddressEntry>
|
||||
#include <QNetworkInterface>
|
||||
#include <QPromise>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
@ -313,6 +316,44 @@ namespace
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
template <typename T>
|
||||
concept HasInfoHashMember = requires (T t) { { t.info_hashes } -> std::convertible_to<InfoHash>; };
|
||||
|
||||
template <typename T>
|
||||
concept HasInfoHashMemberFn = requires (T t) { { t.info_hashes() } -> std::convertible_to<InfoHash>; };
|
||||
|
||||
template <HasInfoHashMember T>
|
||||
InfoHash getInfoHash(const T &t) { return t.info_hashes; }
|
||||
|
||||
template <HasInfoHashMemberFn T>
|
||||
InfoHash getInfoHash(const T &t) { return t.info_hashes(); }
|
||||
|
||||
InfoHash getInfoHash(const lt::add_torrent_params &addTorrentParams)
|
||||
{
|
||||
const bool hasMetadata = (addTorrentParams.ti && addTorrentParams.ti->is_valid());
|
||||
return hasMetadata ? getInfoHash(*addTorrentParams.ti) : InfoHash(addTorrentParams.info_hashes);
|
||||
}
|
||||
#else
|
||||
template <typename T>
|
||||
concept HasInfoHashMember = requires (T t) { { t.info_hash } -> std::convertible_to<InfoHash>; };
|
||||
|
||||
template <typename T>
|
||||
concept HasInfoHashMemberFn = requires (T t) { { t.info_hash() } -> std::convertible_to<InfoHash>; };
|
||||
|
||||
template <HasInfoHashMember T>
|
||||
InfoHash getInfoHash(const T &t) { return t.info_hash; }
|
||||
|
||||
template <HasInfoHashMemberFn T>
|
||||
InfoHash getInfoHash(const T &t) { return t.info_hash(); }
|
||||
|
||||
InfoHash getInfoHash(const lt::add_torrent_params &addTorrentParams)
|
||||
{
|
||||
const bool hasMetadata = (addTorrentParams.ti && addTorrentParams.ti->is_valid());
|
||||
return hasMetadata ? getInfoHash(*addTorrentParams.ti) : InfoHash(addTorrentParams.info_hash);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
|
||||
|
@ -470,11 +511,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), Torrent::NO_SEEDING_TIME_LIMIT
|
||||
, clampValue(Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME))
|
||||
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT
|
||||
, clampValue(Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME))
|
||||
, 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)
|
||||
|
@ -622,7 +663,6 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||
m_fileSearcher = new FileSearcher;
|
||||
m_fileSearcher->moveToThread(m_ioThread.get());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
|
||||
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
|
||||
|
||||
m_torrentContentRemover = new TorrentContentRemover;
|
||||
m_torrentContentRemover->moveToThread(m_ioThread.get());
|
||||
|
@ -1241,7 +1281,7 @@ qreal SessionImpl::globalMaxRatio() const
|
|||
void SessionImpl::setGlobalMaxRatio(qreal ratio)
|
||||
{
|
||||
if (ratio < 0)
|
||||
ratio = -1.;
|
||||
ratio = Torrent::NO_RATIO_LIMIT;
|
||||
|
||||
if (ratio != globalMaxRatio())
|
||||
{
|
||||
|
@ -1257,7 +1297,7 @@ int SessionImpl::globalMaxSeedingMinutes() const
|
|||
|
||||
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
|
||||
{
|
||||
minutes = std::clamp(minutes, Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME);
|
||||
minutes = std::max(minutes, Torrent::NO_SEEDING_TIME_LIMIT);
|
||||
|
||||
if (minutes != globalMaxSeedingMinutes())
|
||||
{
|
||||
|
@ -1273,7 +1313,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
|
|||
|
||||
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
|
||||
{
|
||||
minutes = std::clamp(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME);
|
||||
minutes = std::max(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT);
|
||||
|
||||
if (minutes != globalMaxInactiveSeedingMinutes())
|
||||
{
|
||||
|
@ -1442,10 +1482,8 @@ void SessionImpl::processNextResumeData(ResumeSessionContext *context)
|
|||
LoadTorrentParams resumeData = *loadResumeDataResult;
|
||||
bool needStore = false;
|
||||
|
||||
const InfoHash infoHash = getInfoHash(resumeData.ltAddTorrentParams);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {(resumeData.ltAddTorrentParams.ti
|
||||
? resumeData.ltAddTorrentParams.ti->info_hashes()
|
||||
: resumeData.ltAddTorrentParams.info_hashes)};
|
||||
const bool isHybrid = infoHash.isHybrid();
|
||||
const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
|
||||
const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
|
@ -1493,9 +1531,6 @@ void SessionImpl::processNextResumeData(ResumeSessionContext *context)
|
|||
return;
|
||||
}
|
||||
#else
|
||||
const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
|
||||
? resumeData.ltAddTorrentParams.ti->info_hash()
|
||||
: resumeData.ltAddTorrentParams.info_hash);
|
||||
if (torrentID != TorrentID::fromInfoHash(infoHash))
|
||||
{
|
||||
LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
|
||||
|
@ -1585,16 +1620,23 @@ void SessionImpl::processNextResumeData(ResumeSessionContext *context)
|
|||
#endif
|
||||
|
||||
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
||||
m_loadingTorrents.insert(torrentID, resumeData);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
// this allows to know the being added hybrid torrent by its v1 info hash
|
||||
// without having yet another mapping table
|
||||
m_hybridTorrentsByAltID.insert(torrentIDv1, nullptr);
|
||||
}
|
||||
#endif
|
||||
m_nativeSession->async_add_torrent(resumeData.ltAddTorrentParams);
|
||||
m_addTorrentAlertHandlers.enqueue([this, resumeData = std::move(resumeData)](const lt::add_torrent_alert *alert)
|
||||
{
|
||||
if (alert->error)
|
||||
{
|
||||
const QString msg = QString::fromStdString(alert->message());
|
||||
LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
|
||||
}
|
||||
else
|
||||
{
|
||||
Torrent *torrent = createTorrent(alert->handle, resumeData);
|
||||
m_loadedTorrents.append(torrent);
|
||||
|
||||
LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
|
||||
}
|
||||
});
|
||||
|
||||
++context->processingResumeDataCount;
|
||||
}
|
||||
|
||||
|
@ -1642,11 +1684,7 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
|
|||
auto wakeupCheckTimer = new QTimer(this);
|
||||
connect(wakeupCheckTimer, &QTimer::timeout, this, [this]
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
const bool hasSystemSlept = m_wakeupCheckTimestamp.durationElapsed() > 100s;
|
||||
#else
|
||||
const bool hasSystemSlept = m_wakeupCheckTimestamp.elapsed() > std::chrono::milliseconds(100s).count();
|
||||
#endif
|
||||
if (hasSystemSlept)
|
||||
{
|
||||
LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
|
||||
|
@ -2334,19 +2372,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.");
|
||||
|
@ -2380,31 +2418,6 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
|
|||
}
|
||||
}
|
||||
|
||||
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
||||
{
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (torrent)
|
||||
{
|
||||
torrent->fileSearchFinished(savePath, fileNames);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto loadingTorrentsIter = m_loadingTorrents.find(id);
|
||||
if (loadingTorrentsIter != m_loadingTorrents.end())
|
||||
{
|
||||
LoadTorrentParams ¶ms = loadingTorrentsIter.value();
|
||||
lt::add_torrent_params &p = params.ltAddTorrentParams;
|
||||
|
||||
p.save_path = savePath.toString().toStdString();
|
||||
const TorrentInfo torrentInfo {*p.ti};
|
||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||
for (int i = 0; i < fileNames.size(); ++i)
|
||||
p.renamed_files[nativeIndexes[i]] = fileNames[i].toString().toStdString();
|
||||
|
||||
m_nativeSession->async_add_torrent(p);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage)
|
||||
{
|
||||
if (errorMessage.isEmpty())
|
||||
|
@ -2426,7 +2439,7 @@ Torrent *SessionImpl::getTorrent(const TorrentID &id) const
|
|||
Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const
|
||||
{
|
||||
const auto id = TorrentID::fromInfoHash(infoHash);
|
||||
if (Torrent *torrent = m_torrents.value(id); torrent)
|
||||
if (Torrent *torrent = m_torrents.value(id))
|
||||
return torrent;
|
||||
|
||||
if (!infoHash.isHybrid())
|
||||
|
@ -2543,7 +2556,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
|||
return true;
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {nativeHandle.info_hashes()};
|
||||
const InfoHash infoHash = getInfoHash(nativeHandle);
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
// if magnet link was hybrid initially then it is indexed also by v1 info hash
|
||||
|
@ -2772,14 +2785,6 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
// alternative ID can be useful to find existing torrent in case if hybrid torrent was added by v1 info hash
|
||||
const auto altID = (infoHash.isHybrid() ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
|
||||
|
||||
// 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))
|
||||
{
|
||||
// a duplicate torrent is being added
|
||||
|
@ -2832,11 +2837,12 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
|
||||
p = source.ltAddTorrentParams();
|
||||
|
||||
bool isFindingIncompleteFiles = false;
|
||||
|
||||
const bool useAutoTMM = loadTorrentParams.useAutoTMM;
|
||||
const Path actualSavePath = useAutoTMM ? categorySavePath(loadTorrentParams.category) : loadTorrentParams.savePath;
|
||||
|
||||
bool needFindIncompleteFiles = false;
|
||||
PathList filePaths;
|
||||
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Torrent that is being added with metadata is considered to be added as stopped
|
||||
|
@ -2851,7 +2857,7 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
|
||||
Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
|
||||
|
||||
PathList filePaths = addTorrentParams.filePaths;
|
||||
filePaths = addTorrentParams.filePaths;
|
||||
if (filePaths.isEmpty())
|
||||
{
|
||||
filePaths = torrentInfo.filePaths();
|
||||
|
@ -2896,18 +2902,7 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
}
|
||||
|
||||
if (!loadTorrentParams.hasFinishedStatus)
|
||||
{
|
||||
const Path actualDownloadPath = useAutoTMM
|
||||
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
|
||||
findIncompleteFiles(torrentInfo, actualSavePath, actualDownloadPath, filePaths);
|
||||
isFindingIncompleteFiles = true;
|
||||
}
|
||||
|
||||
if (!isFindingIncompleteFiles)
|
||||
{
|
||||
for (int index = 0; index < filePaths.size(); ++index)
|
||||
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
||||
}
|
||||
needFindIncompleteFiles = true;
|
||||
|
||||
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||
|
@ -2927,8 +2922,6 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
loadTorrentParams.name = QString::fromStdString(p.name);
|
||||
}
|
||||
|
||||
p.save_path = actualSavePath.toString().toStdString();
|
||||
|
||||
if (isAddTrackersEnabled() && !(hasMetadata && p.ti->priv()))
|
||||
{
|
||||
const auto maxTierIter = std::max_element(p.tracker_tiers.cbegin(), p.tracker_tiers.cend());
|
||||
|
@ -2999,26 +2992,80 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
|
||||
m_loadingTorrents.insert(id, loadTorrentParams);
|
||||
if (infoHash.isHybrid())
|
||||
m_hybridTorrentsByAltID.insert(altID, nullptr);
|
||||
if (!isFindingIncompleteFiles)
|
||||
const auto resolveFileNames = [&, this]
|
||||
{
|
||||
if (!needFindIncompleteFiles)
|
||||
return QtFuture::makeReadyValueFuture(FileSearchResult {.savePath = actualSavePath, .fileNames = filePaths});
|
||||
|
||||
const Path actualDownloadPath = loadTorrentParams.useAutoTMM
|
||||
? categoryDownloadPath(loadTorrentParams.category) : loadTorrentParams.downloadPath;
|
||||
return findIncompleteFiles(actualSavePath, actualDownloadPath, filePaths);
|
||||
};
|
||||
|
||||
resolveFileNames().then(this, [this, id, loadTorrentParams = std::move(loadTorrentParams)](const FileSearchResult &result) mutable
|
||||
{
|
||||
lt::add_torrent_params &p = loadTorrentParams.ltAddTorrentParams;
|
||||
|
||||
p.save_path = result.savePath.toString().toStdString();
|
||||
if (p.ti)
|
||||
{
|
||||
const TorrentInfo torrentInfo {*p.ti};
|
||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||
for (int i = 0; i < result.fileNames.size(); ++i)
|
||||
p.renamed_files[nativeIndexes[i]] = result.fileNames[i].toString().toStdString();
|
||||
}
|
||||
|
||||
m_nativeSession->async_add_torrent(p);
|
||||
m_addTorrentAlertHandlers.enqueue([this, loadTorrentParams = std::move(loadTorrentParams)](const lt::add_torrent_alert *alert)
|
||||
{
|
||||
if (alert->error)
|
||||
{
|
||||
const QString msg = QString::fromStdString(alert->message());
|
||||
LogMsg(tr("Failed to add torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
|
||||
|
||||
const InfoHash infoHash = getInfoHash(alert->params);
|
||||
const AddTorrentError::Kind errorKind = (alert->error == lt::errors::duplicate_torrent)
|
||||
? AddTorrentError::DuplicateTorrent : AddTorrentError::Other;
|
||||
emit addTorrentFailed(infoHash, {errorKind, msg});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loadTorrentParams.addToQueueTop)
|
||||
alert->handle.queue_position_top();
|
||||
|
||||
TorrentImpl *torrent = createTorrent(alert->handle, loadTorrentParams);
|
||||
m_loadedTorrents.append(torrent);
|
||||
|
||||
torrent->requestResumeData(lt::torrent_handle::save_info_dict);
|
||||
|
||||
LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentAdded(torrent);
|
||||
|
||||
// The following is useless for newly added magnet
|
||||
if (torrent->hasMetadata())
|
||||
{
|
||||
if (!torrentExportDirectory().isEmpty())
|
||||
exportTorrentFile(torrent, torrentExportDirectory());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionImpl::findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
|
||||
, const Path &downloadPath, const PathList &filePaths) const
|
||||
QFuture<FileSearchResult> SessionImpl::findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths) const
|
||||
{
|
||||
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
|
||||
|
||||
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
|
||||
const PathList originalFileNames = (filePaths.isEmpty() ? torrentInfo.filePaths() : filePaths);
|
||||
QMetaObject::invokeMethod(m_fileSearcher, [=, this]
|
||||
QPromise<FileSearchResult> promise;
|
||||
QFuture<FileSearchResult> future = promise.future();
|
||||
promise.start();
|
||||
QMetaObject::invokeMethod(m_fileSearcher, [=, this, promise = std::move(promise)]() mutable
|
||||
{
|
||||
m_fileSearcher->search(searchId, originalFileNames, savePath, downloadPath, isAppendExtensionEnabled());
|
||||
m_fileSearcher->search(filePaths, savePath, downloadPath, isAppendExtensionEnabled(), promise);
|
||||
promise.finish();
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
void SessionImpl::enablePortMapping()
|
||||
|
@ -3156,6 +3203,33 @@ bool SessionImpl::downloadMetadata(const TorrentDescriptor &torrentDescr)
|
|||
// Adding torrent to libtorrent session
|
||||
m_nativeSession->async_add_torrent(p);
|
||||
m_downloadedMetadata.insert(id, {});
|
||||
m_addTorrentAlertHandlers.enqueue([this](const lt::add_torrent_alert *alert)
|
||||
{
|
||||
if (alert->error)
|
||||
{
|
||||
const QString msg = QString::fromStdString(alert->message());
|
||||
LogMsg(tr("Failed to download torrent metadata. Reason: \"%1\"").arg(msg), Log::WARNING);
|
||||
|
||||
m_downloadedMetadata.remove(getInfoHash(alert->params).toTorrentID());
|
||||
}
|
||||
else
|
||||
{
|
||||
const InfoHash infoHash = getInfoHash(alert->handle);
|
||||
const auto torrentID = TorrentID::fromInfoHash(infoHash);
|
||||
|
||||
if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
|
||||
; downloadedMetadataIter != m_downloadedMetadata.end())
|
||||
{
|
||||
downloadedMetadataIter.value() = alert->handle;
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
// index hybrid magnet links by both v1 and v2 info hashes
|
||||
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
m_downloadedMetadata[altID] = alert->handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5204,8 +5278,6 @@ bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
|
|||
// in case if hybrid torrent was added by v1 info hash
|
||||
const auto altID = (isHybrid ? TorrentID::fromSHA1Hash(infoHash.v1()) : TorrentID());
|
||||
|
||||
if (m_loadingTorrents.contains(id) || (isHybrid && m_loadingTorrents.contains(altID)))
|
||||
return true;
|
||||
if (m_downloadedMetadata.contains(id) || (isHybrid && m_downloadedMetadata.contains(altID)))
|
||||
return true;
|
||||
return findTorrent(infoHash);
|
||||
|
@ -5418,15 +5490,25 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
|
|||
return true;
|
||||
}
|
||||
|
||||
lt::torrent_handle SessionImpl::reloadTorrent(const lt::torrent_handle ¤tHandle, lt::add_torrent_params params)
|
||||
{
|
||||
m_nativeSession->remove_torrent(currentHandle, lt::session::delete_partfile);
|
||||
|
||||
auto *const extensionData = new ExtensionData;
|
||||
params.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
params.storage = customStorageConstructor;
|
||||
#endif
|
||||
|
||||
// libtorrent will post an add_torrent_alert anyway, so we have to add an empty handler to ignore it.
|
||||
m_addTorrentAlertHandlers.enqueue({});
|
||||
return m_nativeSession->add_torrent(std::move(params));
|
||||
}
|
||||
|
||||
void SessionImpl::moveTorrentStorage(const MoveStorageJob &job) const
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
|
||||
#endif
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
const TorrentImpl *torrent = getTorrent(job.torrentHandle);
|
||||
const QString torrentName = (torrent ? torrent->name() : getInfoHash(job.torrentHandle).toTorrentID().toString());
|
||||
LogMsg(tr("Start moving torrent. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, job.path.toString()));
|
||||
|
||||
job.torrentHandle.move_storage(job.path.toString().toStdString(), toNative(job.mode));
|
||||
|
@ -5446,7 +5528,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
|||
|
||||
const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.cend());
|
||||
|
||||
TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
|
||||
TorrentImpl *torrent = getTorrent(finishedJob.torrentHandle);
|
||||
if (torrent)
|
||||
{
|
||||
torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
|
||||
|
@ -5454,8 +5536,9 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
|||
else if (!torrentHasOutstandingJob)
|
||||
{
|
||||
// Last job is completed for torrent that being removing, so actually remove it
|
||||
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
|
||||
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
|
||||
const lt::torrent_handle nativeHandle = finishedJob.torrentHandle;
|
||||
const TorrentID torrentID = getInfoHash(nativeHandle).toTorrentID();
|
||||
const RemovingTorrentData &removingTorrentData = m_removingTorrents[torrentID];
|
||||
if (removingTorrentData.removeOption == TorrentRemoveOption::KeepContent)
|
||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
|
||||
}
|
||||
|
@ -5746,72 +5829,12 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
|||
{
|
||||
++m_receivedAddTorrentAlertsCount;
|
||||
|
||||
if (alert->error)
|
||||
{
|
||||
const QString msg = QString::fromStdString(alert->message());
|
||||
LogMsg(tr("Failed to load torrent. Reason: \"%1\"").arg(msg), Log::WARNING);
|
||||
emit loadTorrentFailed(msg);
|
||||
|
||||
const lt::add_torrent_params ¶ms = alert->params;
|
||||
const bool hasMetadata = (params.ti && params.ti->is_valid());
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {(hasMetadata ? params.ti->info_hashes() : params.info_hashes)};
|
||||
if (infoHash.isHybrid())
|
||||
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
||||
#else
|
||||
const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
|
||||
#endif
|
||||
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(TorrentID::fromInfoHash(infoHash))
|
||||
; loadingTorrentsIter != m_loadingTorrents.cend())
|
||||
{
|
||||
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.constFind(TorrentID::fromInfoHash(infoHash))
|
||||
; downloadedMetadataIter != m_downloadedMetadata.cend())
|
||||
{
|
||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
// index hybrid magnet links by both v1 and v2 info hashes
|
||||
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
m_downloadedMetadata.remove(altID);
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_addTorrentAlertHandlers.isEmpty());
|
||||
if (m_addTorrentAlertHandlers.isEmpty()) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {alert->handle.info_hashes()};
|
||||
#else
|
||||
const InfoHash infoHash {alert->handle.info_hash()};
|
||||
#endif
|
||||
const auto torrentID = TorrentID::fromInfoHash(infoHash);
|
||||
|
||||
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(torrentID)
|
||||
; loadingTorrentsIter != m_loadingTorrents.cend())
|
||||
{
|
||||
const LoadTorrentParams params = loadingTorrentsIter.value();
|
||||
m_loadingTorrents.erase(loadingTorrentsIter);
|
||||
|
||||
Torrent *torrent = createTorrent(alert->handle, params);
|
||||
m_loadedTorrents.append(torrent);
|
||||
}
|
||||
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(torrentID)
|
||||
; downloadedMetadataIter != m_downloadedMetadata.end())
|
||||
{
|
||||
downloadedMetadataIter.value() = alert->handle;
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
// index hybrid magnet links by both v1 and v2 info hashes
|
||||
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
m_downloadedMetadata[altID] = alert->handle;
|
||||
}
|
||||
}
|
||||
if (const AddTorrentAlertHandler handleAlert = m_addTorrentAlertHandlers.dequeue())
|
||||
handleAlert(alert);
|
||||
}
|
||||
|
||||
void SessionImpl::handleAlert(const lt::alert *alert)
|
||||
|
@ -5933,12 +5956,11 @@ void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *alert)
|
|||
--m_numResumeData;
|
||||
}
|
||||
|
||||
const TorrentID torrentID {alert->handle.info_hash()};
|
||||
TorrentImpl *torrent = m_torrents.value(torrentID);
|
||||
TorrentImpl *torrent = getTorrent(alert->handle);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
if (!torrent && (alert->type() == lt::metadata_received_alert::alert_type))
|
||||
{
|
||||
const InfoHash infoHash {alert->handle.info_hashes()};
|
||||
const InfoHash infoHash = getInfoHash(alert->handle);
|
||||
if (infoHash.isHybrid())
|
||||
torrent = m_torrents.value(TorrentID::fromSHA1Hash(infoHash.v1()));
|
||||
}
|
||||
|
@ -5960,42 +5982,17 @@ void SessionImpl::dispatchTorrentAlert(const lt::torrent_alert *alert)
|
|||
|
||||
TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
|
||||
{
|
||||
auto *const torrent = new TorrentImpl(this, m_nativeSession, nativeHandle, params);
|
||||
auto *const torrent = new TorrentImpl(this, nativeHandle, params);
|
||||
m_torrents.insert(torrent->id(), torrent);
|
||||
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
||||
m_hybridTorrentsByAltID.insert(TorrentID::fromSHA1Hash(infoHash.v1()), torrent);
|
||||
|
||||
if (isRestored())
|
||||
{
|
||||
if (params.addToQueueTop)
|
||||
nativeHandle.queue_position_top();
|
||||
|
||||
torrent->requestResumeData(lt::torrent_handle::save_info_dict);
|
||||
|
||||
// The following is useless for newly added magnet
|
||||
if (torrent->hasMetadata())
|
||||
{
|
||||
if (!torrentExportDirectory().isEmpty())
|
||||
exportTorrentFile(torrent, torrentExportDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
if (((torrent->ratioLimit() >= 0) || (torrent->seedingTimeLimit() >= 0))
|
||||
&& !m_seedingLimitTimer->isActive())
|
||||
{
|
||||
m_seedingLimitTimer->start();
|
||||
}
|
||||
|
||||
if (!isRestored())
|
||||
{
|
||||
LogMsg(tr("Restored torrent. Torrent: \"%1\"").arg(torrent->name()));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Added new torrent. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentAdded(torrent);
|
||||
}
|
||||
|
||||
// Torrent could have error just after adding to libtorrent
|
||||
if (torrent->hasError())
|
||||
LogMsg(tr("Torrent errored. Torrent: \"%1\". Error: \"%2\"").arg(torrent->name(), torrent->error()), Log::WARNING);
|
||||
|
@ -6003,6 +6000,11 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
|
|||
return torrent;
|
||||
}
|
||||
|
||||
TorrentImpl *SessionImpl::getTorrent(const lt::torrent_handle &nativeHandle) const
|
||||
{
|
||||
return m_torrents.value(getInfoHash(nativeHandle).toTorrentID());
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*alert*/)
|
||||
{
|
||||
// We cannot consider `torrent_removed_alert` as a starting point for removing content,
|
||||
|
@ -6012,34 +6014,19 @@ void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*a
|
|||
|
||||
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
#else
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||
#endif
|
||||
handleRemovedTorrent(torrentID);
|
||||
handleRemovedTorrent(getInfoHash(*alert).toTorrentID());
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
#else
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||
#endif
|
||||
const TorrentID torrentID = getInfoHash(*alert).toTorrentID();
|
||||
const auto errorMessage = alert->error ? Utils::String::fromLocal8Bit(alert->error.message()) : QString();
|
||||
handleRemovedTorrent(torrentID, errorMessage);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {alert->handle.info_hashes()};
|
||||
#else
|
||||
const InfoHash infoHash {alert->handle.info_hash()};
|
||||
#endif
|
||||
const auto torrentID = TorrentID::fromInfoHash(infoHash);
|
||||
|
||||
const TorrentID torrentID = getInfoHash(alert->handle).toTorrentID();
|
||||
TorrentImpl *const torrent = m_torrents.value(torrentID);
|
||||
if (!torrent) [[unlikely]]
|
||||
return;
|
||||
|
@ -6053,7 +6040,8 @@ void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *
|
|||
|
||||
void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *alert)
|
||||
{
|
||||
const TorrentID torrentID {alert->handle.info_hash()};
|
||||
const InfoHash infoHash = getInfoHash(alert->handle);
|
||||
const TorrentID torrentID = infoHash.toTorrentID();
|
||||
|
||||
bool found = false;
|
||||
if (const auto iter = m_downloadedMetadata.constFind(torrentID); iter != m_downloadedMetadata.cend())
|
||||
|
@ -6062,7 +6050,6 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
|
|||
m_downloadedMetadata.erase(iter);
|
||||
}
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash infoHash {alert->handle.info_hashes()};
|
||||
if (infoHash.isHybrid())
|
||||
{
|
||||
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
|
@ -6084,7 +6071,7 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
|
|||
|
||||
void SessionImpl::handleFileErrorAlert(const lt::file_error_alert *alert)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(alert->handle.info_hash());
|
||||
TorrentImpl *const torrent = getTorrent(alert->handle);
|
||||
if (!torrent)
|
||||
return;
|
||||
|
||||
|
@ -6155,7 +6142,7 @@ void SessionImpl::handlePeerBanAlert(const lt::peer_ban_alert *alert)
|
|||
|
||||
void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *alert)
|
||||
{
|
||||
const TorrentImpl *torrent = m_torrents.value(alert->handle.info_hash());
|
||||
const TorrentImpl *torrent = getTorrent(alert->handle);
|
||||
if (!torrent)
|
||||
return;
|
||||
|
||||
|
@ -6334,14 +6321,8 @@ void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *alert)
|
|||
const Path newPath {QString::fromUtf8(alert->storage_path())};
|
||||
Q_ASSERT(newPath == currentJob.path);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||
#endif
|
||||
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
TorrentImpl *torrent = getTorrent(currentJob.torrentHandle);
|
||||
const QString torrentName = (torrent ? torrent->name() : getInfoHash(currentJob.torrentHandle).toTorrentID().toString());
|
||||
LogMsg(tr("Moved torrent successfully. Torrent: \"%1\". Destination: \"%2\"").arg(torrentName, newPath.toString()));
|
||||
|
||||
handleMoveTorrentStorageJobFinished(newPath);
|
||||
|
@ -6354,14 +6335,8 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
|
|||
const MoveStorageJob ¤tJob = m_moveStorageQueue.constFirst();
|
||||
Q_ASSERT(currentJob.torrentHandle == alert->handle);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||
#endif
|
||||
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
TorrentImpl *torrent = getTorrent(currentJob.torrentHandle);
|
||||
const QString torrentName = (torrent ? torrent->name() : getInfoHash(currentJob.torrentHandle).toTorrentID().toString());
|
||||
const Path currentLocation = (torrent ? torrent->actualStorageLocation()
|
||||
: Path(alert->handle.status(lt::torrent_handle::query_save_path).save_path));
|
||||
const QString errorMessage = QString::fromStdString(alert->message());
|
||||
|
@ -6378,12 +6353,7 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
|
|||
|
||||
for (const lt::torrent_status &status : alert->status)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(status.info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(status.info_hash);
|
||||
#endif
|
||||
TorrentImpl *const torrent = m_torrents.value(id);
|
||||
TorrentImpl *const torrent = getTorrent(status.handle);
|
||||
if (!torrent)
|
||||
continue;
|
||||
|
||||
|
@ -6427,7 +6397,7 @@ void SessionImpl::handleI2PAlert(const lt::i2p_alert *alert) const
|
|||
|
||||
void SessionImpl::handleTrackerAlert(const lt::tracker_alert *alert)
|
||||
{
|
||||
TorrentImpl *torrent = m_torrents.value(alert->handle.info_hash());
|
||||
TorrentImpl *torrent = getTorrent(alert->handle);
|
||||
if (!torrent)
|
||||
return;
|
||||
|
||||
|
@ -6453,8 +6423,9 @@ void SessionImpl::handleTrackerAlert(const lt::tracker_alert *alert)
|
|||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert)
|
||||
{
|
||||
const auto torrentIDv1 = TorrentID::fromSHA1Hash(alert->metadata->info_hashes().v1);
|
||||
const auto torrentIDv2 = TorrentID::fromSHA256Hash(alert->metadata->info_hashes().v2);
|
||||
const InfoHash infoHash = getInfoHash(*alert->metadata);
|
||||
const auto torrentIDv1 = TorrentID::fromSHA1Hash(infoHash.v1());
|
||||
const auto torrentIDv2 = TorrentID::fromSHA256Hash(infoHash.v2());
|
||||
TorrentImpl *torrent1 = m_torrents.value(torrentIDv1);
|
||||
TorrentImpl *torrent2 = m_torrents.value(torrentIDv2);
|
||||
if (torrent2)
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
@ -44,6 +45,7 @@
|
|||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QPointer>
|
||||
#include <QQueue>
|
||||
#include <QSet>
|
||||
#include <QThreadPool>
|
||||
|
||||
|
@ -61,12 +63,16 @@ class QString;
|
|||
class QTimer;
|
||||
class QUrl;
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
class BandwidthScheduler;
|
||||
class FileSearcher;
|
||||
class FilterParserThread;
|
||||
class FreeDiskSpaceChecker;
|
||||
class NativeSessionExtension;
|
||||
|
||||
struct FileSearchResult;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class MoveStorageMode;
|
||||
|
@ -478,8 +484,9 @@ namespace BitTorrent
|
|||
|
||||
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
|
||||
|
||||
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
|
||||
, const Path &downloadPath, const PathList &filePaths = {}) const;
|
||||
lt::torrent_handle reloadTorrent(const lt::torrent_handle ¤tHandle, lt::add_torrent_params params);
|
||||
|
||||
QFuture<FileSearchResult> findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths = {}) const;
|
||||
|
||||
void enablePortMapping();
|
||||
void disablePortMapping();
|
||||
|
@ -514,7 +521,6 @@ namespace BitTorrent
|
|||
void generateResumeData();
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||
|
||||
private:
|
||||
|
@ -604,6 +610,7 @@ namespace BitTorrent
|
|||
#endif
|
||||
|
||||
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
TorrentImpl *getTorrent(const lt::torrent_handle &nativeHandle) const;
|
||||
|
||||
void saveResumeData();
|
||||
void saveTorrentsQueue();
|
||||
|
@ -810,11 +817,13 @@ namespace BitTorrent
|
|||
FileSearcher *m_fileSearcher = nullptr;
|
||||
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||
|
||||
using AddTorrentAlertHandler = std::function<void (const lt::add_torrent_alert *alert)>;
|
||||
QQueue<AddTorrentAlertHandler> m_addTorrentAlertHandlers;
|
||||
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
|
||||
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
||||
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
||||
QHash<TorrentID, TorrentID> m_changedTorrentIDs;
|
||||
QMap<QString, CategoryOptions> m_categories;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -45,6 +45,8 @@ class QByteArray;
|
|||
class QDateTime;
|
||||
class QUrl;
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class DownloadPriority;
|
||||
|
@ -132,8 +134,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;
|
||||
|
||||
|
@ -275,10 +275,7 @@ namespace BitTorrent
|
|||
virtual bool isDHTDisabled() const = 0;
|
||||
virtual bool isPEXDisabled() const = 0;
|
||||
virtual bool isLSDDisabled() const = 0;
|
||||
virtual QList<PeerInfo> peers() const = 0;
|
||||
virtual QBitArray pieces() const = 0;
|
||||
virtual QBitArray downloadingPieces() const = 0;
|
||||
virtual QList<int> pieceAvailability() const = 0;
|
||||
virtual qreal distributedCopies() const = 0;
|
||||
virtual qreal maxRatio() const = 0;
|
||||
virtual int maxSeedingTime() const = 0;
|
||||
|
@ -325,10 +322,10 @@ namespace BitTorrent
|
|||
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
|
||||
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
|
||||
|
||||
virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
|
||||
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
|
||||
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
|
||||
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
|
||||
virtual QFuture<QList<PeerInfo>> fetchPeerInfo() const = 0;
|
||||
virtual QFuture<QList<QUrl>> fetchURLSeeds() const = 0;
|
||||
virtual QFuture<QList<int>> fetchPieceAvailability() const = 0;
|
||||
virtual QFuture<QBitArray> fetchDownloadingPieces() const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isRunning() const;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-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
|
||||
|
@ -34,6 +34,8 @@
|
|||
#include "abstractfilestorage.h"
|
||||
#include "downloadpriority.h"
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentHandler : public QObject, public AbstractFileStorage
|
||||
|
@ -52,8 +54,7 @@ namespace BitTorrent
|
|||
* This is not the same as torrrent availability, it is just a fraction of pieces
|
||||
* that can be downloaded right now. It varies between 0 to 1.
|
||||
*/
|
||||
virtual QList<qreal> availableFileFractions() const = 0;
|
||||
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
|
||||
virtual QFuture<QList<qreal>> fetchAvailableFileFractions() const = 0;
|
||||
|
||||
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
|
||||
virtual void flushCache() const = 0;
|
||||
|
|
|
@ -124,18 +124,20 @@ void TorrentCreator::run()
|
|||
// need to sort the file names by natural sort order
|
||||
QStringList dirs = {m_params.sourcePath.data()};
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
// Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
|
||||
#endif
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), dirFilters, QDirIterator::Subdirectories};
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
const QString filePath = dirIter.next();
|
||||
dirs.append(filePath);
|
||||
const QFileInfo dirInfo = dirIter.nextFileInfo();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// .lnk to directory
|
||||
// Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
if (dirInfo.isShortcut())
|
||||
continue;
|
||||
#endif
|
||||
|
||||
const QString dirPath = dirInfo.filePath();
|
||||
dirs.append(dirPath);
|
||||
}
|
||||
std::sort(dirs.begin(), dirs.end(), naturalLessThan);
|
||||
|
||||
|
@ -146,19 +148,29 @@ void TorrentCreator::run()
|
|||
{
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters fileFilters {QDir::Files};
|
||||
#endif
|
||||
QDirIterator fileIter {dir, fileFilters};
|
||||
QDirIterator fileIter {dir, QDir::Files};
|
||||
while (fileIter.hasNext())
|
||||
{
|
||||
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
||||
const Path filePath {fileInfo.filePath()};
|
||||
qint64 fileSize = fileInfo.size();
|
||||
|
||||
const Path relFilePath = parentPath.relativePathOf(Path(fileInfo.filePath()));
|
||||
#ifdef Q_OS_WIN
|
||||
// .lnk to file
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
if (fileInfo.isShortcut())
|
||||
continue;
|
||||
|
||||
// file symbolic link
|
||||
// QFileInfo::size() failed to return the target file size
|
||||
// and we need to redirect it manually
|
||||
if (fileInfo.isSymbolicLink())
|
||||
fileSize = QFileInfo(fileInfo.symLinkTarget()).size();
|
||||
#endif
|
||||
|
||||
const Path relFilePath = parentPath.relativePathOf(filePath);
|
||||
tmpNames.append(relFilePath.toString());
|
||||
fileSizeMap[tmpNames.last()] = fileInfo.size();
|
||||
fileSizeMap[tmpNames.last()] = fileSize;
|
||||
}
|
||||
|
||||
std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan);
|
||||
|
@ -174,8 +186,8 @@ void TorrentCreator::run()
|
|||
#ifdef QBT_USES_LIBTORRENT2
|
||||
lt::create_torrent newTorrent {fs, m_params.pieceSize, toNativeTorrentFormatFlag(m_params.torrentFormat)};
|
||||
#else
|
||||
lt::create_torrent newTorrent(fs, m_params.pieceSize, m_params.paddedFileSizeLimit
|
||||
, (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {}));
|
||||
lt::create_torrent newTorrent {fs, m_params.pieceSize, m_params.paddedFileSizeLimit
|
||||
, (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})};
|
||||
#endif
|
||||
|
||||
// Add url seeds
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -51,7 +51,9 @@
|
|||
#include <QByteArray>
|
||||
#include <QCache>
|
||||
#include <QDebug>
|
||||
#include <QFuture>
|
||||
#include <QPointer>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
@ -67,6 +69,7 @@
|
|||
#include "common.h"
|
||||
#include "downloadpriority.h"
|
||||
#include "extensiondata.h"
|
||||
#include "filesearcher.h"
|
||||
#include "loadtorrentparams.h"
|
||||
#include "ltqbitarray.h"
|
||||
#include "lttypecast.h"
|
||||
|
@ -287,11 +290,9 @@ namespace
|
|||
|
||||
// TorrentImpl
|
||||
|
||||
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
|
||||
TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
|
||||
: Torrent(session)
|
||||
, m_session(session)
|
||||
, m_nativeSession(nativeSession)
|
||||
, m_nativeHandle(nativeHandle)
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
, m_infoHash(m_nativeHandle.info_hashes())
|
||||
|
@ -1465,48 +1466,11 @@ bool TorrentImpl::isLSDDisabled() const
|
|||
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
|
||||
}
|
||||
|
||||
QList<PeerInfo> TorrentImpl::peers() const
|
||||
{
|
||||
std::vector<lt::peer_info> nativePeers;
|
||||
m_nativeHandle.get_peer_info(nativePeers);
|
||||
|
||||
QList<PeerInfo> peers;
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
peers.append(PeerInfo(peer, pieces()));
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
QBitArray TorrentImpl::pieces() const
|
||||
{
|
||||
return m_pieces;
|
||||
}
|
||||
|
||||
QBitArray TorrentImpl::downloadingPieces() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
std::vector<lt::partial_piece_info> queue;
|
||||
m_nativeHandle.get_download_queue(queue);
|
||||
|
||||
QBitArray result {piecesCount()};
|
||||
for (const lt::partial_piece_info &info : queue)
|
||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<int> TorrentImpl::pieceAvailability() const
|
||||
{
|
||||
std::vector<int> avail;
|
||||
m_nativeHandle.piece_availability(avail);
|
||||
|
||||
return {avail.cbegin(), avail.cend()};
|
||||
}
|
||||
|
||||
qreal TorrentImpl::distributedCopies() const
|
||||
{
|
||||
return m_nativeStatus.distributed_copies;
|
||||
|
@ -1549,7 +1513,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
|
||||
|
@ -1751,12 +1716,6 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
|
|||
m_nativeHandle.prioritize_pieces(piecePriorities);
|
||||
}
|
||||
|
||||
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
endReceivedMetadataHandling(savePath, fileNames);
|
||||
}
|
||||
|
||||
TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
||||
{
|
||||
const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
|
||||
|
@ -1876,16 +1835,6 @@ void TorrentImpl::reload()
|
|||
{
|
||||
try
|
||||
{
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
const auto queuePos = m_nativeHandle.queue_position();
|
||||
|
||||
m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
|
||||
|
||||
lt::add_torrent_params p = m_ltAddTorrentParams;
|
||||
p.flags |= lt::torrent_flags::update_subscribe
|
||||
| lt::torrent_flags::override_trackers
|
||||
|
@ -1905,19 +1854,21 @@ void TorrentImpl::reload()
|
|||
p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
|
||||
}
|
||||
|
||||
auto *const extensionData = new ExtensionData;
|
||||
p.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
const auto queuePos = m_nativeHandle.queue_position();
|
||||
|
||||
m_nativeStatus = extensionData->status;
|
||||
m_nativeHandle = m_session->reloadTorrent(m_nativeHandle, std::move(p));
|
||||
m_nativeStatus = static_cast<ExtensionData *>(m_nativeHandle.userdata())->status;
|
||||
|
||||
if (queuePos >= lt::queue_position_t {})
|
||||
m_nativeHandle.queue_position_set(queuePos);
|
||||
m_nativeStatus.queue_position = queuePos;
|
||||
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
updateState();
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
|
@ -2150,7 +2101,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
|
||||
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
|
||||
// than the list we usually cache, so we have to request it from the appropriate source.
|
||||
fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
fetchURLSeeds().then(this, [this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
}
|
||||
|
||||
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
|
||||
|
@ -2197,7 +2148,12 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||
filePaths[i] = Path(it->second);
|
||||
}
|
||||
|
||||
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
|
||||
m_session->findIncompleteFiles(savePath(), downloadPath(), filePaths).then(this
|
||||
, [this](const FileSearchResult &result)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
endReceivedMetadataHandling(result.savePath, result.fileNames);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2712,8 +2668,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 +2681,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 +2694,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)
|
||||
{
|
||||
|
@ -2930,9 +2880,9 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
|
|||
return {};
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
|
||||
QFuture<QList<PeerInfo>> TorrentImpl::fetchPeerInfo() const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -2947,13 +2897,12 @@ void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHand
|
|||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
|
||||
QFuture<QList<QUrl>> TorrentImpl::fetchURLSeeds() const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
|
||||
return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -2967,13 +2916,12 @@ void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler)
|
|||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
|
||||
QFuture<QList<int>> TorrentImpl::fetchPieceAvailability() const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
|
||||
return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -2984,13 +2932,12 @@ void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> result
|
|||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
|
||||
QFuture<QBitArray> TorrentImpl::fetchDownloadingPieces() const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -3009,13 +2956,12 @@ void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultH
|
|||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
|
||||
QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
|
||||
{
|
||||
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
|
||||
return {};
|
||||
|
@ -3049,8 +2995,7 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)>
|
|||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
||||
|
@ -3090,47 +3035,17 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
|||
manageActualFilePaths();
|
||||
}
|
||||
|
||||
QList<qreal> TorrentImpl::availableFileFractions() const
|
||||
template <typename Func>
|
||||
QFuture<std::invoke_result_t<Func>> TorrentImpl::invokeAsync(Func &&func) const
|
||||
{
|
||||
Q_ASSERT(hasMetadata());
|
||||
|
||||
const int filesCount = this->filesCount();
|
||||
if (filesCount <= 0) return {};
|
||||
|
||||
const QList<int> piecesAvailability = pieceAvailability();
|
||||
// libtorrent returns empty array for seeding only torrents
|
||||
if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
|
||||
|
||||
QList<qreal> res;
|
||||
res.reserve(filesCount);
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
QPromise<std::invoke_result_t<Func>> promise;
|
||||
const auto future = promise.future();
|
||||
promise.start();
|
||||
m_session->invokeAsync([func = std::forward<Func>(func), promise = std::move(promise)]() mutable
|
||||
{
|
||||
const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
|
||||
|
||||
int availablePieces = 0;
|
||||
for (const int piece : filePieces)
|
||||
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
|
||||
|
||||
const qreal availability = filePieces.isEmpty()
|
||||
? 1 // the file has no pieces, so it is available by default
|
||||
: static_cast<qreal>(availablePieces) / filePieces.size();
|
||||
res.push_back(availability);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Func, typename Callback>
|
||||
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
|
||||
{
|
||||
m_session->invokeAsync([session = m_session
|
||||
, func = std::move(func)
|
||||
, resultHandler = std::move(resultHandler)
|
||||
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
|
||||
{
|
||||
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
|
||||
{
|
||||
if (thisTorrent)
|
||||
resultHandler(result);
|
||||
});
|
||||
promise.addResult(func());
|
||||
promise.finish();
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
|
|
@ -94,8 +94,7 @@ namespace BitTorrent
|
|||
Q_DISABLE_COPY_MOVE(TorrentImpl)
|
||||
|
||||
public:
|
||||
TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
~TorrentImpl() override;
|
||||
|
||||
bool isValid() const;
|
||||
|
@ -203,10 +202,7 @@ namespace BitTorrent
|
|||
bool isDHTDisabled() const override;
|
||||
bool isPEXDisabled() const override;
|
||||
bool isLSDDisabled() const override;
|
||||
QList<PeerInfo> peers() const override;
|
||||
QBitArray pieces() const override;
|
||||
QBitArray downloadingPieces() const override;
|
||||
QList<int> pieceAvailability() const override;
|
||||
qreal distributedCopies() const override;
|
||||
qreal maxRatio() const override;
|
||||
int maxSeedingTime() const override;
|
||||
|
@ -220,7 +216,6 @@ namespace BitTorrent
|
|||
int connectionsCount() const override;
|
||||
int connectionsLimit() const override;
|
||||
qlonglong nextAnnounce() const override;
|
||||
QList<qreal> availableFileFractions() const override;
|
||||
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
|
@ -258,11 +253,11 @@ namespace BitTorrent
|
|||
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
|
||||
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
|
||||
|
||||
void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override;
|
||||
void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override;
|
||||
void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override;
|
||||
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
|
||||
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override;
|
||||
QFuture<QList<PeerInfo>> fetchPeerInfo() const override;
|
||||
QFuture<QList<QUrl>> fetchURLSeeds() const override;
|
||||
QFuture<QList<int>> fetchPieceAvailability() const override;
|
||||
QFuture<QBitArray> fetchDownloadingPieces() const override;
|
||||
QFuture<QList<qreal>> fetchAvailableFileFractions() const override;
|
||||
|
||||
bool needSaveResumeData() const;
|
||||
|
||||
|
@ -278,7 +273,6 @@ namespace BitTorrent
|
|||
void requestResumeData(lt::resume_data_flags_t flags = {});
|
||||
void deferredRequestResumeData();
|
||||
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
|
||||
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
|
||||
TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
|
||||
void resetTrackerEntryStatuses();
|
||||
|
||||
|
@ -326,11 +320,10 @@ namespace BitTorrent
|
|||
|
||||
nonstd::expected<lt::entry, QString> exportTorrent() const;
|
||||
|
||||
template <typename Func, typename Callback>
|
||||
void invokeAsync(Func func, Callback resultHandler) const;
|
||||
template <typename Func>
|
||||
QFuture<std::invoke_result_t<Func>> invokeAsync(Func &&func) const;
|
||||
|
||||
SessionImpl *const m_session = nullptr;
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
lt::torrent_handle m_nativeHandle;
|
||||
mutable lt::torrent_status m_nativeStatus;
|
||||
TorrentState m_state = TorrentState::Unknown;
|
||||
|
|
|
@ -42,7 +42,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
|
|||
, m_downloadProcess {new QProcess(this)}
|
||||
{
|
||||
m_downloadProcess->setProcessEnvironment(m_manager->proxyEnvironment());
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
#ifdef Q_OS_UNIX
|
||||
m_downloadProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
|
||||
|
@ -55,7 +55,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
|
|||
url
|
||||
};
|
||||
// Launch search
|
||||
m_downloadProcess->start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
||||
m_downloadProcess->start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly);
|
||||
}
|
||||
|
||||
void SearchDownloadHandler::downloadProcessFinished(int exitcode)
|
||||
|
|
|
@ -71,8 +71,8 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
|
|||
{
|
||||
// Load environment variables (proxy)
|
||||
m_searchProcess->setProcessEnvironment(m_manager->proxyEnvironment());
|
||||
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executablePath.data());
|
||||
#ifdef Q_OS_UNIX
|
||||
m_searchProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -546,7 +546,7 @@ void SearchPluginManager::update()
|
|||
{
|
||||
QProcess nova;
|
||||
nova.setProcessEnvironment(proxyEnvironment());
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
#ifdef Q_OS_UNIX
|
||||
nova.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
|
@ -556,7 +556,7 @@ void SearchPluginManager::update()
|
|||
(engineLocation() / Path(u"/nova2.py"_s)).toString(),
|
||||
u"--capabilities"_s
|
||||
};
|
||||
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
||||
nova.start(Utils::ForeignApps::pythonInfo().executablePath.data(), params, QIODevice::ReadOnly);
|
||||
nova.waitForFinished();
|
||||
|
||||
const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput());
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou
|
||||
* Copyright (C) 2018-2025 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -30,6 +30,7 @@
|
|||
#include "foreignapps.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <algorithm>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
@ -40,27 +41,30 @@
|
|||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <QDir>
|
||||
#include <QScopeGuard>
|
||||
#endif
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/path.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/bytearray.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include "base/utils/compare.h"
|
||||
#endif
|
||||
|
||||
using namespace Utils::ForeignApps;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool testPythonInstallation(const QString &exeName, PythonInfo &info)
|
||||
bool testPythonInstallation(const Path &exePath, PythonInfo &info)
|
||||
{
|
||||
info = {};
|
||||
|
||||
QProcess proc;
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
#ifdef Q_OS_UNIX
|
||||
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
proc.start(exeName, {u"--version"_s}, QIODevice::ReadOnly);
|
||||
proc.start(exePath.data(), {u"--version"_s}, QIODevice::ReadOnly);
|
||||
if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit))
|
||||
{
|
||||
QByteArray procOutput = proc.readAllStandardOutput();
|
||||
|
@ -83,9 +87,9 @@ namespace
|
|||
if (!version.isValid())
|
||||
return false;
|
||||
|
||||
info = {exeName, version};
|
||||
info = {.executablePath = exePath, .version = version};
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Found Python executable. Name: \"%1\". Version: \"%2\"")
|
||||
.arg(info.executableName, info.version.toString()), Log::INFO);
|
||||
.arg(info.executablePath.toString(), info.version.toString()), Log::INFO);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -100,178 +104,160 @@ namespace
|
|||
SYSTEM_64BIT
|
||||
};
|
||||
|
||||
QStringList getRegSubkeys(const HKEY handle)
|
||||
PathList getRegSubkeys(const HKEY handle)
|
||||
{
|
||||
QStringList keys;
|
||||
PathList keys;
|
||||
|
||||
DWORD cSubKeys = 0;
|
||||
DWORD cMaxSubKeyLen = 0;
|
||||
LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
const LSTATUS result = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
if (res == ERROR_SUCCESS)
|
||||
if (result == ERROR_SUCCESS)
|
||||
{
|
||||
++cMaxSubKeyLen; // For null character
|
||||
LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
|
||||
DWORD cName;
|
||||
LPWSTR lpName = new WCHAR[cMaxSubKeyLen] {0};
|
||||
[[maybe_unused]] const auto lpNameGuard = qScopeGuard([&lpName] { delete[] lpName; });
|
||||
|
||||
keys.reserve(cSubKeys);
|
||||
|
||||
for (DWORD i = 0; i < cSubKeys; ++i)
|
||||
{
|
||||
cName = cMaxSubKeyLen;
|
||||
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
|
||||
DWORD cName = cMaxSubKeyLen;
|
||||
const LSTATUS res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
|
||||
if (res == ERROR_SUCCESS)
|
||||
keys.push_back(QString::fromWCharArray(lpName));
|
||||
keys.append(Path(QString::fromWCharArray(lpName)));
|
||||
}
|
||||
|
||||
delete[] lpName;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
QString getRegValue(const HKEY handle, const QString &name = {})
|
||||
Path getRegValue(const HKEY handle, const QString &name = {})
|
||||
{
|
||||
const std::wstring nameWStr = name.toStdWString();
|
||||
DWORD type = 0;
|
||||
DWORD cbData = 0;
|
||||
|
||||
// Discover the size of the value
|
||||
::RegQueryValueExW(handle, nameWStr.c_str(), NULL, &type, NULL, &cbData);
|
||||
DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1;
|
||||
LPWSTR lpData = new WCHAR[cBuffer];
|
||||
LONG res = ::RegQueryValueExW(handle, nameWStr.c_str(), NULL, &type, reinterpret_cast<LPBYTE>(lpData), &cbData);
|
||||
|
||||
QString result;
|
||||
const DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1;
|
||||
LPWSTR lpData = new WCHAR[cBuffer] {0};
|
||||
[[maybe_unused]] const auto lpDataGuard = qScopeGuard([&lpData] { delete[] lpData; });
|
||||
|
||||
const LSTATUS res = ::RegQueryValueExW(handle, nameWStr.c_str(), NULL, &type, reinterpret_cast<LPBYTE>(lpData), &cbData);
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
lpData[cBuffer - 1] = 0;
|
||||
result = QString::fromWCharArray(lpData);
|
||||
}
|
||||
delete[] lpData;
|
||||
return Path(QString::fromWCharArray(lpData));
|
||||
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
QString pythonSearchReg(const REG_SEARCH_TYPE type)
|
||||
PathList pythonSearchReg(const REG_SEARCH_TYPE type)
|
||||
{
|
||||
HKEY hkRoot;
|
||||
if (type == USER)
|
||||
hkRoot = HKEY_CURRENT_USER;
|
||||
else
|
||||
hkRoot = HKEY_LOCAL_MACHINE;
|
||||
const HKEY hkRoot = (type == USER) ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
|
||||
const REGSAM samDesired = KEY_READ
|
||||
| ((type == SYSTEM_64BIT) ? KEY_WOW64_64KEY : KEY_WOW64_32KEY);
|
||||
PathList ret;
|
||||
|
||||
REGSAM samDesired = KEY_READ;
|
||||
if (type == SYSTEM_32BIT)
|
||||
samDesired |= KEY_WOW64_32KEY;
|
||||
else if (type == SYSTEM_64BIT)
|
||||
samDesired |= KEY_WOW64_64KEY;
|
||||
|
||||
QString path;
|
||||
LONG res = 0;
|
||||
HKEY hkPythonCore;
|
||||
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
|
||||
|
||||
if (res == ERROR_SUCCESS)
|
||||
HKEY hkPythonCore {0};
|
||||
if (::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore) == ERROR_SUCCESS)
|
||||
{
|
||||
QStringList versions = getRegSubkeys(hkPythonCore);
|
||||
versions.sort();
|
||||
[[maybe_unused]] const auto hkPythonCoreGuard = qScopeGuard([&hkPythonCore] { ::RegCloseKey(hkPythonCore); });
|
||||
|
||||
bool found = false;
|
||||
while (!found && !versions.empty())
|
||||
// start with the largest version
|
||||
PathList versions = getRegSubkeys(hkPythonCore);
|
||||
// ordinary sort won't suffice, it needs to sort ["3.9", "3.10"] correctly
|
||||
const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> comparator;
|
||||
std::sort(versions.begin(), versions.end(), [&comparator](const Path &left, const Path &right)
|
||||
{
|
||||
const std::wstring version = QString(versions.takeLast() + u"\\InstallPath").toStdWString();
|
||||
return comparator(left.data(), right.data());
|
||||
});
|
||||
|
||||
HKEY hkInstallPath;
|
||||
res = ::RegOpenKeyExW(hkPythonCore, version.c_str(), 0, samDesired, &hkInstallPath);
|
||||
ret.reserve(versions.size() * 2);
|
||||
|
||||
if (res == ERROR_SUCCESS)
|
||||
while (!versions.empty())
|
||||
{
|
||||
const std::wstring version = (versions.takeLast() / Path(u"InstallPath"_s)).toString().toStdWString();
|
||||
|
||||
HKEY hkInstallPath {0};
|
||||
if (::RegOpenKeyExW(hkPythonCore, version.c_str(), 0, samDesired, &hkInstallPath) == ERROR_SUCCESS)
|
||||
{
|
||||
qDebug("Detected possible Python v%ls location", version.c_str());
|
||||
path = getRegValue(hkInstallPath);
|
||||
::RegCloseKey(hkInstallPath);
|
||||
[[maybe_unused]] const auto hkInstallPathGuard = qScopeGuard([&hkInstallPath] { ::RegCloseKey(hkInstallPath); });
|
||||
|
||||
if (!path.isEmpty())
|
||||
{
|
||||
const QDir baseDir {path};
|
||||
const Path basePath = getRegValue(hkInstallPath);
|
||||
if (basePath.isEmpty())
|
||||
continue;
|
||||
|
||||
if (baseDir.exists(u"python3.exe"_s))
|
||||
{
|
||||
found = true;
|
||||
path = baseDir.filePath(u"python3.exe"_s);
|
||||
}
|
||||
else if (baseDir.exists(u"python.exe"_s))
|
||||
{
|
||||
found = true;
|
||||
path = baseDir.filePath(u"python.exe"_s);
|
||||
}
|
||||
}
|
||||
if (const Path path = (basePath / Path(u"python3.exe"_s)); path.exists())
|
||||
ret.append(path);
|
||||
if (const Path path = (basePath / Path(u"python.exe"_s)); path.exists())
|
||||
ret.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
path = QString();
|
||||
|
||||
::RegCloseKey(hkPythonCore);
|
||||
}
|
||||
|
||||
return path;
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString findPythonPath()
|
||||
PathList searchPythonPaths()
|
||||
{
|
||||
QString path = pythonSearchReg(USER);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
path = pythonSearchReg(SYSTEM_32BIT);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
path = pythonSearchReg(SYSTEM_64BIT);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
// From registry
|
||||
PathList ret = pythonSearchReg(USER)
|
||||
+ pythonSearchReg(SYSTEM_64BIT)
|
||||
+ pythonSearchReg(SYSTEM_32BIT);
|
||||
|
||||
// Fallback: Detect python from default locations
|
||||
const QFileInfoList dirs = QDir(u"C:/"_s).entryInfoList({u"Python*"_s}, QDir::Dirs, (QDir::Name | QDir::Reversed));
|
||||
for (const QFileInfo &info : dirs)
|
||||
{
|
||||
const QString py3Path {info.absolutePath() + u"/python3.exe"};
|
||||
if (QFile::exists(py3Path))
|
||||
return py3Path;
|
||||
const Path absPath {info.absolutePath()};
|
||||
|
||||
const QString pyPath {info.absolutePath() + u"/python.exe"};
|
||||
if (QFile::exists(pyPath))
|
||||
return pyPath;
|
||||
if (const Path path = (absPath / Path(u"python3.exe"_s)); path.exists())
|
||||
ret.append(path);
|
||||
if (const Path path = (absPath / Path(u"python.exe"_s)); path.exists())
|
||||
ret.append(path);
|
||||
}
|
||||
|
||||
return {};
|
||||
return ret;
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
bool Utils::ForeignApps::PythonInfo::isValid() const
|
||||
{
|
||||
return (!executableName.isEmpty() && version.isValid());
|
||||
return (executablePath.isValid() && version.isValid());
|
||||
}
|
||||
|
||||
bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
|
||||
{
|
||||
return (version >= Version {3, 9, 0});
|
||||
return (version >= MINIMUM_SUPPORTED_VERSION);
|
||||
}
|
||||
|
||||
PythonInfo Utils::ForeignApps::pythonInfo()
|
||||
{
|
||||
static PythonInfo pyInfo;
|
||||
|
||||
const QString preferredPythonPath = Preferences::instance()->getPythonExecutablePath().toString();
|
||||
if (pyInfo.isValid() && (preferredPythonPath == pyInfo.executableName))
|
||||
const Path preferredPythonPath = Preferences::instance()->getPythonExecutablePath();
|
||||
if (pyInfo.isValid() && (preferredPythonPath == pyInfo.executablePath))
|
||||
return pyInfo;
|
||||
|
||||
const QString invalidVersionMessage = QCoreApplication::translate("Utils::ForeignApps"
|
||||
, "Python failed to meet minimum version requirement. Path: \"%1\". Found version: \"%2\". Minimum supported version: \"%3\".");
|
||||
|
||||
if (!preferredPythonPath.isEmpty())
|
||||
{
|
||||
if (testPythonInstallation(preferredPythonPath, pyInfo))
|
||||
return pyInfo;
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable. Path: \"%1\".")
|
||||
.arg(preferredPythonPath), Log::WARNING);
|
||||
{
|
||||
if (pyInfo.isSupportedVersion())
|
||||
return pyInfo;
|
||||
|
||||
LogMsg(invalidVersionMessage.arg(pyInfo.executablePath.toString()
|
||||
, pyInfo.version.toString(), PythonInfo::MINIMUM_SUPPORTED_VERSION.toString()), Log::WARNING);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable. Path: \"%1\".")
|
||||
.arg(preferredPythonPath.toString()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -279,24 +265,40 @@ PythonInfo Utils::ForeignApps::pythonInfo()
|
|||
|
||||
if (!pyInfo.isValid())
|
||||
{
|
||||
if (testPythonInstallation(u"python3"_s, pyInfo))
|
||||
return pyInfo;
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python3` executable in PATH environment variable. PATH: \"%1\"")
|
||||
.arg(qEnvironmentVariable("PATH")), Log::INFO);
|
||||
// search in `PATH` environment variable
|
||||
const QString exeNames[] = {u"python3"_s, u"python"_s};
|
||||
for (const QString &exeName : exeNames)
|
||||
{
|
||||
if (testPythonInstallation(Path(exeName), pyInfo))
|
||||
{
|
||||
if (pyInfo.isSupportedVersion())
|
||||
return pyInfo;
|
||||
|
||||
if (testPythonInstallation(u"python"_s, pyInfo))
|
||||
return pyInfo;
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in PATH environment variable. PATH: \"%1\"")
|
||||
.arg(qEnvironmentVariable("PATH")), Log::INFO);
|
||||
LogMsg(invalidVersionMessage.arg(pyInfo.executablePath.toString()
|
||||
, pyInfo.version.toString(), PythonInfo::MINIMUM_SUPPORTED_VERSION.toString()), Log::INFO);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `%1` executable in PATH environment variable. PATH: \"%2\"")
|
||||
.arg(exeName, qEnvironmentVariable("PATH")), Log::INFO);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (testPythonInstallation(findPythonPath(), pyInfo))
|
||||
return pyInfo;
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find `python` executable in Windows Registry."), Log::INFO);
|
||||
for (const Path &path : asConst(searchPythonPaths()))
|
||||
{
|
||||
if (testPythonInstallation(path, pyInfo))
|
||||
{
|
||||
if (pyInfo.isSupportedVersion())
|
||||
return pyInfo;
|
||||
|
||||
LogMsg(invalidVersionMessage.arg(pyInfo.executablePath.toString()
|
||||
, pyInfo.version.toString(), PythonInfo::MINIMUM_SUPPORTED_VERSION.toString()), Log::INFO);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Failed to find Python executable"), Log::WARNING);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou
|
||||
* Copyright (C) 2018-2025 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -32,6 +32,7 @@
|
|||
#include <QString>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/path.h"
|
||||
#include "base/utils/version.h"
|
||||
|
||||
namespace Utils::ForeignApps
|
||||
|
@ -45,8 +46,10 @@ namespace Utils::ForeignApps
|
|||
bool isValid() const;
|
||||
bool isSupportedVersion() const;
|
||||
|
||||
QString executableName;
|
||||
Path executablePath;
|
||||
Version version;
|
||||
|
||||
inline static const Version MINIMUM_SUPPORTED_VERSION {3, 9, 0};
|
||||
};
|
||||
|
||||
PythonInfo pythonInfo();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -61,7 +61,7 @@ QString Utils::String::fromLocal8Bit(const std::string_view string)
|
|||
|
||||
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 1))
|
||||
return QRegularExpression::wildcardToRegularExpression(pattern
|
||||
, (QRegularExpression::UnanchoredWildcardConversion | QRegularExpression::NonPathWildcardConversion));
|
||||
#else
|
||||
|
|
|
@ -32,12 +32,14 @@
|
|||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <QtVersionChecks>
|
||||
#include <QAction>
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QFuture>
|
||||
#include <QList>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
|
@ -242,14 +244,13 @@ public:
|
|||
return QList<qreal>(filesCount(), 0);
|
||||
}
|
||||
|
||||
QList<qreal> availableFileFractions() const override
|
||||
QFuture<QList<qreal>> fetchAvailableFileFractions() const override
|
||||
{
|
||||
return QList<qreal>(filesCount(), 0);
|
||||
}
|
||||
|
||||
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override
|
||||
{
|
||||
resultHandler(availableFileFractions());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
return QtFuture::makeReadyValueFuture(QList<qreal>(filesCount(), 0));
|
||||
#else
|
||||
return QtFuture::makeReadyFuture(QList<qreal>(filesCount(), 0));
|
||||
#endif
|
||||
}
|
||||
|
||||
void prioritizeFiles(const QList<BitTorrent::DownloadPriority> &priorities) override
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace
|
|||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
TORRENT_CONTENT_REMOVE_OPTION,
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
MEMORY_WORKING_SET_LIMIT,
|
||||
#endif
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -209,7 +209,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
|||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
|
||||
session->setResumeDataStorageType(m_comboBoxResumeDataStorage.currentData().value<BitTorrent::ResumeDataStorageType>());
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
// Physical memory (RAM) usage limit
|
||||
app()->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value());
|
||||
#endif
|
||||
|
@ -494,12 +494,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
m_comboBoxTorrentContentRemoveOption.setCurrentIndex(m_comboBoxTorrentContentRemoveOption.findData(QVariant::fromValue(session->torrentContentRemoveOption())));
|
||||
addRow(TORRENT_CONTENT_REMOVE_OPTION, tr("Torrent content removing mode"), &m_comboBoxTorrentContentRemoveOption);
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
// Physical memory (RAM) usage limit
|
||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||
m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits<int>::max());
|
||||
m_spinBoxMemoryWorkingSetLimit.setSuffix(tr(" MiB"));
|
||||
m_spinBoxMemoryWorkingSetLimit.setToolTip(tr("This option is less effective on Linux"));
|
||||
m_spinBoxMemoryWorkingSetLimit.setValue(app()->memoryWorkingSetLimit());
|
||||
addRow(MEMORY_WORKING_SET_LIMIT, (tr("Physical memory (RAM) usage limit") + u' ' + makeLink(u"https://wikipedia.org/wiki/Working_set", u"(?)"))
|
||||
, &m_spinBoxMemoryWorkingSetLimit);
|
||||
|
|
|
@ -93,7 +93,7 @@ private:
|
|||
QSpinBox m_spinBoxHashingThreads;
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
QSpinBox m_spinBoxMemoryWorkingSetLimit;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1616,14 +1616,14 @@ void MainWindow::on_actionSearchWidget_triggered()
|
|||
#ifdef Q_OS_WIN
|
||||
const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
|
||||
, tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
|
||||
.arg(pyInfo.version.toString(), u"3.9.0")
|
||||
.arg(pyInfo.version.toString(), Utils::ForeignApps::PythonInfo::MINIMUM_SUPPORTED_VERSION.toString())
|
||||
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
||||
if (buttonPressed == QMessageBox::Yes)
|
||||
installPython();
|
||||
#else
|
||||
QMessageBox::information(this, tr("Old Python Runtime")
|
||||
, tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
|
||||
.arg(pyInfo.version.toString(), u"3.9.0"));
|
||||
.arg(pyInfo.version.toString(), Utils::ForeignApps::PythonInfo::MINIMUM_SUPPORTED_VERSION.toString()));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1031,7 +1031,7 @@
|
|||
<item>
|
||||
<widget class="QGroupBox" name="duplicateTorrentGroup">
|
||||
<property name="title">
|
||||
<string>When duplicate torrent is being added</string>
|
||||
<string>When adding a duplicate torrent</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="duplicateTorrentBoxLayout">
|
||||
<item>
|
||||
|
@ -1047,7 +1047,7 @@
|
|||
<item>
|
||||
<widget class="QCheckBox" name="checkConfirmMergeTrackers">
|
||||
<property name="text">
|
||||
<string>Ask for merging trackers when torrent is being added manually</string>
|
||||
<string>Ask to merge trackers for manually added torrent</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
@ -3028,9 +3028,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>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QFuture>
|
||||
#include <QHeaderView>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
|
@ -406,10 +407,13 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
|||
return;
|
||||
|
||||
using TorrentPtr = QPointer<const BitTorrent::Torrent>;
|
||||
torrent->fetchPeerInfo([this, torrent = TorrentPtr(torrent)](const QList<BitTorrent::PeerInfo> &peers)
|
||||
torrent->fetchPeerInfo().then(this, [this, torrent = TorrentPtr(torrent)](const QList<BitTorrent::PeerInfo> &peers)
|
||||
{
|
||||
if (torrent != m_properties->getCurrentTorrent())
|
||||
if (const BitTorrent::Torrent *currentTorrent = m_properties->getCurrentTorrent();
|
||||
!currentTorrent || (currentTorrent != torrent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove I2P peers since they will be completely reloaded.
|
||||
for (const QStandardItem *item : asConst(m_I2PPeerItems))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -32,6 +32,7 @@
|
|||
#include <QClipboard>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFuture>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
|
@ -440,10 +441,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())
|
||||
|
@ -471,17 +472,18 @@ void PropertiesWidget::loadDynamicData()
|
|||
|
||||
if (m_torrent->hasMetadata())
|
||||
{
|
||||
using TorrentPtr = QPointer<BitTorrent::Torrent>;
|
||||
|
||||
m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave()));
|
||||
|
||||
if (!m_torrent->isFinished() && !m_torrent->isStopped() && !m_torrent->isQueued() && !m_torrent->isChecking())
|
||||
{
|
||||
// Pieces availability
|
||||
showPiecesAvailability(true);
|
||||
m_torrent->fetchPieceAvailability([this, torrent = TorrentPtr(m_torrent)](const QList<int> &pieceAvailability)
|
||||
|
||||
using TorrentPtr = QPointer<BitTorrent::Torrent>;
|
||||
m_torrent->fetchPieceAvailability().then(this
|
||||
, [this, torrent = TorrentPtr(m_torrent)](const QList<int> &pieceAvailability)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
if (m_torrent && (m_torrent == torrent))
|
||||
m_piecesAvailability->setAvailability(pieceAvailability);
|
||||
});
|
||||
|
||||
|
@ -496,9 +498,11 @@ void PropertiesWidget::loadDynamicData()
|
|||
qreal progress = m_torrent->progress() * 100.;
|
||||
m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + u'%');
|
||||
|
||||
m_torrent->fetchDownloadingPieces([this, torrent = TorrentPtr(m_torrent)](const QBitArray &downloadingPieces)
|
||||
using TorrentPtr = QPointer<BitTorrent::Torrent>;
|
||||
m_torrent->fetchDownloadingPieces().then(this
|
||||
, [this, torrent = TorrentPtr(m_torrent)](const QBitArray &downloadingPieces)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
if (m_torrent && (m_torrent == torrent))
|
||||
m_downloadedPieces->setProgress(m_torrent->pieces(), downloadingPieces);
|
||||
});
|
||||
}
|
||||
|
@ -525,9 +529,9 @@ void PropertiesWidget::loadUrlSeeds()
|
|||
return;
|
||||
|
||||
using TorrentPtr = QPointer<BitTorrent::Torrent>;
|
||||
m_torrent->fetchURLSeeds([this, torrent = TorrentPtr(m_torrent)](const QList<QUrl> &urlSeeds)
|
||||
m_torrent->fetchURLSeeds().then(this, [this, torrent = TorrentPtr(m_torrent)](const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
if (torrent != m_torrent)
|
||||
if (!m_torrent || (m_torrent != torrent))
|
||||
return;
|
||||
|
||||
m_ui->listWebSeeds->clear();
|
||||
|
|
|
@ -333,7 +333,7 @@ void RSSWidget::on_newFeedButton_clicked()
|
|||
|
||||
// Ask for feed URL
|
||||
const QString clipText = qApp->clipboard()->text();
|
||||
const QString defaultURL = Net::DownloadManager::hasSupportedScheme(clipText) ? clipText : u"http://"_s;
|
||||
const QString defaultURL = Net::DownloadManager::hasSupportedScheme(clipText) ? clipText : u"https://"_s;
|
||||
|
||||
RSS::Feed *newFeed = nullptr;
|
||||
RSSFeedDialog dialog {this};
|
||||
|
@ -623,58 +623,60 @@ void RSSWidget::renderArticle(const RSS::Article *article) const
|
|||
{
|
||||
Q_ASSERT(article);
|
||||
|
||||
const QString articleLink = article->link();
|
||||
const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::Highlight).name();
|
||||
const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::HighlightedText).name();
|
||||
const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::AlternateBase).name();
|
||||
|
||||
QString html =
|
||||
u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>" +
|
||||
u"<div style='background-color: \"%1\"; font-weight: bold; color: \"%2\";'>%3</div>"_s.arg(highlightedBaseColor, highlightedBaseTextColor, article->title());
|
||||
if (article->date().isValid())
|
||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Date: "), QLocale::system().toString(article->date().toLocalTime()));
|
||||
QString html = u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>"
|
||||
+ u"<div style='background-color: \"%1\"; font-weight: bold; color: \"%2\";'>%3</div>"_s.arg(highlightedBaseColor, highlightedBaseTextColor, article->title());
|
||||
if (const QDateTime articleDate = article->date(); articleDate.isValid())
|
||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Date: "), QLocale::system().toString(articleDate.toLocalTime(), QLocale::ShortFormat));
|
||||
if (m_ui->feedListWidget->currentItem() == m_ui->feedListWidget->stickyUnreadItem())
|
||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Feed: "), article->feed()->title());
|
||||
if (!article->author().isEmpty())
|
||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Author: "), article->author());
|
||||
if (const QString articleAuthor = article->author(); !articleAuthor.isEmpty())
|
||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Author: "), articleAuthor);
|
||||
if (!articleLink.isEmpty())
|
||||
html += u"<div style='background-color: \"%1\";'><a href='%2' target='_blank'><b>%3</b></a></div>"_s.arg(alternateBaseColor, articleLink, tr("Open link"));
|
||||
html += u"</div>"
|
||||
u"<div style='margin-left: 5px; margin-right: 5px;'>";
|
||||
if (Qt::mightBeRichText(article->description()))
|
||||
if (QString description = article->description(); Qt::mightBeRichText(description))
|
||||
{
|
||||
html += article->description();
|
||||
html += description;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString description = article->description();
|
||||
QRegularExpression rx;
|
||||
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
|
||||
rx.setPatternOptions(QRegularExpression::InvertedGreedinessOption
|
||||
| QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
rx.setPattern(u"\\[img\\](.+)\\[/img\\]"_s);
|
||||
description = description.replace(rx, u"<img src=\"\\1\">"_s);
|
||||
description.replace(rx, u"<img src=\"\\1\">"_s);
|
||||
|
||||
rx.setPattern(u"\\[url=(\")?(.+)\\1\\]"_s);
|
||||
description = description.replace(rx, u"<a href=\"\\2\">"_s);
|
||||
description = description.replace(u"[/url]"_s, u"</a>"_s, Qt::CaseInsensitive);
|
||||
description.replace(rx, u"<a href=\"\\2\">"_s)
|
||||
.replace(u"[/url]"_s, u"</a>"_s, Qt::CaseInsensitive);
|
||||
|
||||
rx.setPattern(u"\\[(/)?([bius])\\]"_s);
|
||||
description = description.replace(rx, u"<\\1\\2>"_s);
|
||||
description.replace(rx, u"<\\1\\2>"_s);
|
||||
|
||||
rx.setPattern(u"\\[color=(\")?(.+)\\1\\]"_s);
|
||||
description = description.replace(rx, u"<span style=\"color:\\2\">"_s);
|
||||
description = description.replace(u"[/color]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||
description.replace(rx, u"<span style=\"color:\\2\">"_s)
|
||||
.replace(u"[/color]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||
|
||||
rx.setPattern(u"\\[size=(\")?(.+)\\d\\1\\]"_s);
|
||||
description = description.replace(rx, u"<span style=\"font-size:\\2px\">"_s);
|
||||
description = description.replace(u"[/size]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||
description.replace(rx, u"<span style=\"font-size:\\2px\">"_s)
|
||||
.replace(u"[/size]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||
|
||||
html += u"<pre>" + description + u"</pre>";
|
||||
}
|
||||
html += u"</div>";
|
||||
|
||||
// Supplement relative URLs to absolute ones
|
||||
const QUrl url {article->link()};
|
||||
const QUrl url {articleLink};
|
||||
const QString baseUrl = url.toString(QUrl::RemovePath | QUrl::RemoveQuery);
|
||||
convertRelativeUrlToAbsolute(html, baseUrl);
|
||||
html += u"</div>";
|
||||
|
||||
m_ui->textBrowser->setHtml(html);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include <QFileIconProvider>
|
||||
#include <QFileInfo>
|
||||
#include <QFuture>
|
||||
#include <QIcon>
|
||||
#include <QMimeData>
|
||||
#include <QPointer>
|
||||
|
@ -219,18 +220,14 @@ void TorrentContentModel::updateFilesAvailability()
|
|||
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
|
||||
|
||||
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
|
||||
m_contentHandler->fetchAvailableFileFractions([this, handler = HandlerPtr(m_contentHandler)](const QList<qreal> &availableFileFractions)
|
||||
m_contentHandler->fetchAvailableFileFractions().then(this
|
||||
, [this, handler = HandlerPtr(m_contentHandler)](const QList<qreal> &availableFileFractions)
|
||||
{
|
||||
if (handler != m_contentHandler)
|
||||
return;
|
||||
|
||||
Q_ASSERT(m_filesIndex.size() == availableFileFractions.size());
|
||||
// XXX: Why is this necessary?
|
||||
if (m_filesIndex.size() != availableFileFractions.size()) [[unlikely]]
|
||||
if (!m_contentHandler || (m_contentHandler != handler))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_filesIndex.size(); ++i)
|
||||
m_filesIndex[i]->setAvailability(availableFileFractions[i]);
|
||||
m_filesIndex[i]->setAvailability(availableFileFractions.value(i, 0));
|
||||
// Update folders progress in the tree
|
||||
m_rootItem->recalculateProgress();
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -42,6 +42,7 @@
|
|||
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
#include <QFuture>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QScopeGuard>
|
||||
|
@ -309,16 +310,16 @@ void TrackerListModel::populate()
|
|||
m_items->emplace_back(std::make_shared<Item>(u"** [LSD] **", privateTorrentMessage));
|
||||
|
||||
using TorrentPtr = QPointer<const BitTorrent::Torrent>;
|
||||
m_torrent->fetchPeerInfo([this, torrent = TorrentPtr(m_torrent)](const QList<BitTorrent::PeerInfo> &peers)
|
||||
m_torrent->fetchPeerInfo().then(this, [this, torrent = TorrentPtr(m_torrent)](const QList<BitTorrent::PeerInfo> &peers)
|
||||
{
|
||||
if (torrent != m_torrent)
|
||||
if (!m_torrent || (m_torrent != torrent))
|
||||
return;
|
||||
|
||||
// XXX: libtorrent should provide this info...
|
||||
// Count peers from DHT, PeX, LSD
|
||||
uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, peersDHT = 0, peersPeX = 0, peersLSD = 0;
|
||||
for (const BitTorrent::PeerInfo &peer : peers)
|
||||
{
|
||||
// XXX: libtorrent should provide this info...
|
||||
// Count peers from DHT, PeX, LSD
|
||||
uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, peersDHT = 0, peersPeX = 0, peersLSD = 0;
|
||||
for (const BitTorrent::PeerInfo &peer : peers)
|
||||
{
|
||||
if (peer.isConnecting())
|
||||
continue;
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -176,9 +176,7 @@ void Utils::Gui::openFolderSelect(const Path &path)
|
|||
const int lineMaxLength = 64;
|
||||
|
||||
QProcess proc;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
proc.start(u"xdg-mime"_s, {u"query"_s, u"default"_s, u"inode/directory"_s});
|
||||
proc.waitForFinished();
|
||||
const auto output = QString::fromLocal8Bit(proc.readLine(lineMaxLength).simplified());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018-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
|
||||
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "synccontroller.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaObject>
|
||||
|
@ -745,7 +746,7 @@ void SyncController::torrentPeersAction()
|
|||
QVariantMap data;
|
||||
QVariantHash peers;
|
||||
|
||||
const QList<BitTorrent::PeerInfo> peersList = torrent->peers();
|
||||
const QList<BitTorrent::PeerInfo> peersList = torrent->fetchPeerInfo().takeResult();
|
||||
|
||||
bool resolvePeerCountries = Preferences::instance()->resolvePeerCountries();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018-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
|
||||
|
@ -32,6 +32,7 @@
|
|||
#include <functional>
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QFuture>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
|
@ -180,9 +181,11 @@ namespace
|
|||
QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0;
|
||||
for (const BitTorrent::PeerInfo &peer : asConst(torrent->peers()))
|
||||
const QList<BitTorrent::PeerInfo> peersList = torrent->fetchPeerInfo().takeResult();
|
||||
for (const BitTorrent::PeerInfo &peer : peersList)
|
||||
{
|
||||
if (peer.isConnecting()) continue;
|
||||
if (peer.isConnecting())
|
||||
continue;
|
||||
|
||||
if (peer.isSeed())
|
||||
{
|
||||
|
@ -520,8 +523,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()},
|
||||
|
@ -727,7 +730,7 @@ void TorrentsController::filesAction()
|
|||
{
|
||||
const QList<BitTorrent::DownloadPriority> priorities = torrent->filePriorities();
|
||||
const QList<qreal> fp = torrent->filesProgress();
|
||||
const QList<qreal> fileAvailability = torrent->availableFileFractions();
|
||||
const QList<qreal> fileAvailability = torrent->fetchAvailableFileFractions().takeResult();
|
||||
const BitTorrent::TorrentInfo info = torrent->info();
|
||||
for (const int index : asConst(fileIndexes))
|
||||
{
|
||||
|
@ -796,7 +799,7 @@ void TorrentsController::pieceStatesAction()
|
|||
for (int i = 0; i < states.size(); ++i)
|
||||
pieceStates.append(static_cast<int>(states[i]) * 2);
|
||||
|
||||
const QBitArray dlstates = torrent->downloadingPieces();
|
||||
const QBitArray dlstates = torrent->fetchDownloadingPieces().takeResult();
|
||||
for (int i = 0; i < states.size(); ++i)
|
||||
{
|
||||
if (dlstates[i])
|
||||
|
|
|
@ -34,12 +34,15 @@ export default [
|
|||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-var": "error",
|
||||
"object-shorthand": ["error", "consistent"],
|
||||
"operator-assignment": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-template": "error",
|
||||
"radix": "error",
|
||||
"require-await": "error",
|
||||
"PreferArrowFunctions/prefer-arrow-functions": "error",
|
||||
"Stylistic/no-extra-semi": "error",
|
||||
"Stylistic/no-mixed-operators": [
|
||||
"error",
|
||||
{
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Escape":
|
||||
|
@ -26,13 +26,13 @@
|
|||
if (hash === null)
|
||||
return;
|
||||
|
||||
$("peers").focus();
|
||||
document.getElementById("peers").focus();
|
||||
|
||||
$("addPeersOk").addEventListener("click", (e) => {
|
||||
document.getElementById("addPeersOk").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const peers = $("peers").value.trim().split(/[\r\n]+/);
|
||||
const peers = document.getElementById("peers").value.trim().split(/[\r\n]+/);
|
||||
if (peers.length === 0)
|
||||
return;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Escape":
|
||||
|
@ -22,8 +22,8 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("trackersUrls").focus();
|
||||
$("addTrackersButton").addEventListener("click", (e) => {
|
||||
document.getElementById("trackersUrls").focus();
|
||||
document.getElementById("addTrackersButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
|||
method: "POST",
|
||||
body: new URLSearchParams({
|
||||
hash: new URLSearchParams(window.location.search).get("hash"),
|
||||
urls: $("trackersUrls").value
|
||||
urls: document.getElementById("trackersUrls").value
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Escape":
|
||||
|
@ -22,15 +22,15 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("urls").focus();
|
||||
$("addWebSeedsButton").addEventListener("click", (e) => {
|
||||
document.getElementById("urls").focus();
|
||||
document.getElementById("addWebSeedsButton").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
fetch("api/v2/torrents/addWebSeeds", {
|
||||
method: "POST",
|
||||
body: new URLSearchParams({
|
||||
hash: new URLSearchParams(window.location.search).get("hash"),
|
||||
urls: $("urls").value.split("\n").map(w => encodeURIComponent(w.trim())).filter(w => (w.length > 0)).join("|")
|
||||
urls: document.getElementById("urls").value.split("\n").map(w => encodeURIComponent(w.trim())).filter(w => (w.length > 0)).join("|")
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEventListener("click", (e) => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.getElementById("cancelBtn").focus();
|
||||
document.getElementById("cancelBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
$("confirmBtn").addEventListener("click", (e) => {
|
||||
document.getElementById("confirmBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEventListener("click", (e) => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.getElementById("cancelBtn").focus();
|
||||
document.getElementById("cancelBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
$("confirmBtn").addEventListener("click", (e) => {
|
||||
document.getElementById("confirmBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEventListener("click", (e) => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.getElementById("cancelBtn").focus();
|
||||
document.getElementById("cancelBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
$("confirmBtn").addEventListener("click", (e) => {
|
||||
document.getElementById("confirmBtn").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
|
|
@ -12,18 +12,18 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const host = searchParams.get("host");
|
||||
|
||||
$("confirmDeleteTrackerText").textContent = "QBT_TR(Are you sure you want to remove tracker %1 from all torrents?)QBT_TR[CONTEXT=TrackersFilterWidget]".replace("%1", host);
|
||||
document.getElementById("confirmDeleteTrackerText").textContent = "QBT_TR(Are you sure you want to remove tracker %1 from all torrents?)QBT_TR[CONTEXT=TrackersFilterWidget]".replace("%1", host);
|
||||
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEventListener("click", (e) => {
|
||||
document.getElementById("cancelBtn").focus();
|
||||
document.getElementById("cancelBtn").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
$("confirmBtn").addEventListener("click", (e) => {
|
||||
document.getElementById("confirmBtn").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
fetch("api/v2/torrents/removeTrackers", {
|
||||
|
|
|
@ -755,19 +755,6 @@ td.generalLabel {
|
|||
}
|
||||
}
|
||||
|
||||
.piecesbarWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.piecesbarCanvas {
|
||||
height: 100%;
|
||||
image-rendering: pixelated;
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#watched_folders_tab {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
|
|
@ -167,22 +167,22 @@
|
|||
if (encodedUrls !== null) {
|
||||
const urls = encodedUrls.split("|").map(decodeURIComponent);
|
||||
if (urls.length > 0)
|
||||
$("urls").value = urls.join("\n");
|
||||
document.getElementById("urls").value = urls.join("\n");
|
||||
}
|
||||
|
||||
let submitted = false;
|
||||
|
||||
$("downloadForm").addEventListener("submit", () => {
|
||||
$("startTorrentHidden").value = $("startTorrent").checked ? "false" : "true";
|
||||
document.getElementById("downloadForm").addEventListener("submit", (event) => {
|
||||
document.getElementById("startTorrentHidden").value = document.getElementById("startTorrent").checked ? "false" : "true";
|
||||
|
||||
$("dlLimitHidden").value = Number($("dlLimitText").value) * 1024;
|
||||
$("upLimitHidden").value = Number($("upLimitText").value) * 1024;
|
||||
document.getElementById("dlLimitHidden").value = Number(document.getElementById("dlLimitText").value) * 1024;
|
||||
document.getElementById("upLimitHidden").value = Number(document.getElementById("upLimitText").value) * 1024;
|
||||
|
||||
$("download_spinner").style.display = "block";
|
||||
document.getElementById("download_spinner").style.display = "block";
|
||||
submitted = true;
|
||||
});
|
||||
|
||||
$("download_frame").addEventListener("load", () => {
|
||||
document.getElementById("download_frame").addEventListener("load", (event) => {
|
||||
if (submitted)
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("applyButton").click();
|
||||
document.getElementById("applyButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -48,7 +48,7 @@
|
|||
|
||||
const hashes = new URLSearchParams(window.location.search).get("hashes").split("|");
|
||||
const setDlLimit = () => {
|
||||
const limit = Number($("dllimitUpdatevalue").value) * 1024;
|
||||
const limit = Number(document.getElementById("dllimitUpdatevalue").value) * 1024;
|
||||
if (hashes[0] === "global") {
|
||||
fetch("api/v2/transfer/setDownloadLimit", {
|
||||
method: "POST",
|
||||
|
@ -56,7 +56,7 @@
|
|||
limit: limit
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
|
||||
|
@ -72,7 +72,7 @@
|
|||
limit: limit
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
|
||||
|
@ -81,7 +81,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
$("dllimitUpdatevalue").focus();
|
||||
document.getElementById("dllimitUpdatevalue").focus();
|
||||
|
||||
MochaUI.addDlLimitSlider(hashes);
|
||||
</script>
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("submitButton").click();
|
||||
document.getElementById("submitButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -30,16 +30,16 @@
|
|||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const currentUrl = searchParams.get("url");
|
||||
|
||||
$("url").value = currentUrl;
|
||||
$("url").focus();
|
||||
$("url").setSelectionRange(0, currentUrl.length);
|
||||
document.getElementById("url").value = currentUrl;
|
||||
document.getElementById("url").focus();
|
||||
document.getElementById("url").setSelectionRange(0, currentUrl.length);
|
||||
|
||||
$("submitButton").addEventListener("click", (e) => {
|
||||
document.getElementById("submitButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const newUrl = $("url").value.trim();
|
||||
const newUrl = document.getElementById("url").value.trim();
|
||||
if (newUrl === "") {
|
||||
alert("QBT_TR(URL cannot be empty)QBT_TR[CONTEXT=RSSWidget]");
|
||||
return;
|
||||
|
@ -50,7 +50,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$("submitButton").disabled = true;
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
|
||||
fetch("api/v2/rss/setFeedURL", {
|
||||
method: "POST",
|
||||
|
@ -64,7 +64,7 @@
|
|||
alert((response.status === 409)
|
||||
? await response.text()
|
||||
: "QBT_TR(Unable to update URL)QBT_TR[CONTEXT=RSSWidget]");
|
||||
$("submitButton").disabled = false;
|
||||
document.getElementById("submitButton").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("editTrackerButton").click();
|
||||
document.getElementById("editTrackerButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -31,10 +31,10 @@
|
|||
if (currentUrl === null)
|
||||
return;
|
||||
|
||||
$("trackerUrl").value = currentUrl;
|
||||
$("trackerUrl").focus();
|
||||
document.getElementById("trackerUrl").value = currentUrl;
|
||||
document.getElementById("trackerUrl").focus();
|
||||
|
||||
$("editTrackerButton").addEventListener("click", (e) => {
|
||||
document.getElementById("editTrackerButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
|||
body: new URLSearchParams({
|
||||
hash: searchParams.get("hash"),
|
||||
origUrl: currentUrl,
|
||||
newUrl: $("trackerUrl").value
|
||||
newUrl: document.getElementById("trackerUrl").value
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("editWebSeedButton").click();
|
||||
document.getElementById("editWebSeedButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -28,10 +28,10 @@
|
|||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const origUrl = searchParams.get("url");
|
||||
$("url").value = decodeURIComponent(origUrl);
|
||||
$("url").focus();
|
||||
document.getElementById("url").value = decodeURIComponent(origUrl);
|
||||
document.getElementById("url").focus();
|
||||
|
||||
$("editWebSeedButton").addEventListener("click", (e) => {
|
||||
document.getElementById("editWebSeedButton").addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
fetch("api/v2/torrents/editWebSeed", {
|
||||
|
@ -39,7 +39,7 @@
|
|||
body: new URLSearchParams({
|
||||
hash: searchParams.get("hash"),
|
||||
origUrl: origUrl,
|
||||
newUrl: encodeURIComponent($("url").value.trim())
|
||||
newUrl: encodeURIComponent(document.getElementById("url").value.trim())
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<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/lib/jszip.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>
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("categoryNameButton").click();
|
||||
document.getElementById("categoryNameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -38,25 +38,25 @@
|
|||
if (!uriCategoryName)
|
||||
return;
|
||||
|
||||
$("categoryName").disabled = true;
|
||||
$("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
|
||||
$("savePath").value = window.qBittorrent.Misc.escapeHtml(uriSavePath);
|
||||
$("savePath").focus();
|
||||
document.getElementById("categoryName").disabled = true;
|
||||
document.getElementById("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
|
||||
document.getElementById("savePath").value = window.qBittorrent.Misc.escapeHtml(uriSavePath);
|
||||
document.getElementById("savePath").focus();
|
||||
}
|
||||
else if (uriAction === "createSubcategory") {
|
||||
$("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
|
||||
$("categoryName").focus();
|
||||
document.getElementById("categoryName").value = window.qBittorrent.Misc.escapeHtml(uriCategoryName);
|
||||
document.getElementById("categoryName").focus();
|
||||
}
|
||||
else {
|
||||
$("categoryName").focus();
|
||||
document.getElementById("categoryName").focus();
|
||||
}
|
||||
|
||||
$("categoryNameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("categoryNameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const savePath = $("savePath").value.trim();
|
||||
const categoryName = $("categoryName").value.trim();
|
||||
const savePath = document.getElementById("savePath").value.trim();
|
||||
const categoryName = document.getElementById("categoryName").value.trim();
|
||||
|
||||
const verifyCategoryName = (name) => {
|
||||
if ((name === null) || (name === ""))
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("submitButton").click();
|
||||
document.getElementById("submitButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -27,19 +27,19 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("feedURL").focus();
|
||||
$("submitButton").addEventListener("click", (e) => {
|
||||
document.getElementById("feedURL").focus();
|
||||
document.getElementById("submitButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const feedURL = $("feedURL").value.trim();
|
||||
const feedURL = document.getElementById("feedURL").value.trim();
|
||||
if (feedURL === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$("submitButton").disabled = true;
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
|
||||
const path = new URLSearchParams(window.location.search).get("path");
|
||||
fetch("api/v2/rss/addFeed", {
|
||||
|
@ -53,7 +53,7 @@
|
|||
if (!response.ok) {
|
||||
if (response.status === 409)
|
||||
alert(await response.text());
|
||||
$("submitButton").disabled = false;
|
||||
document.getElementById("submitButton").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("submitButton").click();
|
||||
document.getElementById("submitButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -28,19 +28,19 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("folderName").focus();
|
||||
$("submitButton").addEventListener("click", (e) => {
|
||||
document.getElementById("folderName").focus();
|
||||
document.getElementById("submitButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const folderName = $("folderName").value.trim();
|
||||
const folderName = document.getElementById("folderName").value.trim();
|
||||
if (folderName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$("submitButton").disabled = true;
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
|
||||
const path = new URLSearchParams(window.location.search).get("path");
|
||||
fetch("api/v2/rss/addFolder", {
|
||||
|
@ -53,7 +53,7 @@
|
|||
if (!response.ok) {
|
||||
if (response.status === 409)
|
||||
alert(await response.text());
|
||||
$("submitButton").disabled = false;
|
||||
document.getElementById("submitButton").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("submitButton").click();
|
||||
document.getElementById("submitButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -27,18 +27,18 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("name").focus();
|
||||
$("submitButton").addEventListener("click", (e) => {
|
||||
document.getElementById("name").focus();
|
||||
document.getElementById("submitButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const name = $("name").value.trim();
|
||||
const name = document.getElementById("name").value.trim();
|
||||
if (name === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
$("submitButton").disabled = true;
|
||||
document.getElementById("submitButton").disabled = true;
|
||||
|
||||
fetch("api/v2/rss/setRule", {
|
||||
method: "POST",
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("tagNameButton").click();
|
||||
document.getElementById("tagNameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -31,15 +31,15 @@
|
|||
const uriAction = window.qBittorrent.Misc.safeTrim(searchParams.get("action"));
|
||||
|
||||
if (uriAction === "create")
|
||||
$("legendText").textContent = "QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]";
|
||||
document.getElementById("legendText").textContent = "QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]";
|
||||
|
||||
$("tagName").focus();
|
||||
document.getElementById("tagName").focus();
|
||||
|
||||
$("tagNameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("tagNameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const tagName = $("tagName").value.trim();
|
||||
const tagName = document.getElementById("tagName").value.trim();
|
||||
|
||||
const verifyTagName = (name) => {
|
||||
if ((name === null) || (name === ""))
|
||||
|
@ -64,7 +64,7 @@
|
|||
tags: tagName
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
|
||||
|
@ -83,7 +83,7 @@
|
|||
tags: tagName
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("renameButton").click();
|
||||
document.getElementById("renameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -31,15 +31,15 @@
|
|||
const name = searchParams.get("name");
|
||||
// set text field to current value
|
||||
if (name !== null)
|
||||
$("rename").value = name;
|
||||
document.getElementById("rename").value = name;
|
||||
|
||||
$("rename").focus();
|
||||
$("renameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("rename").focus();
|
||||
document.getElementById("renameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const name = $("rename").value.trim();
|
||||
const name = document.getElementById("rename").value.trim();
|
||||
if ((name === null) || (name === ""))
|
||||
return;
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("renameButton").click();
|
||||
document.getElementById("renameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -29,16 +29,16 @@
|
|||
|
||||
const oldPath = new URLSearchParams(window.location.search).get("oldPath");
|
||||
|
||||
$("rename").value = oldPath;
|
||||
$("rename").focus();
|
||||
$("rename").setSelectionRange(0, oldPath.length);
|
||||
document.getElementById("rename").value = oldPath;
|
||||
document.getElementById("rename").focus();
|
||||
document.getElementById("rename").setSelectionRange(0, oldPath.length);
|
||||
|
||||
$("renameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("renameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const newPath = $("rename").value.trim();
|
||||
const newPath = document.getElementById("rename").value.trim();
|
||||
if (newPath === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
|
@ -49,7 +49,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$("renameButton").disabled = true;
|
||||
document.getElementById("renameButton").disabled = true;
|
||||
|
||||
fetch("api/v2/rss/moveItem", {
|
||||
method: "POST",
|
||||
|
@ -62,7 +62,7 @@
|
|||
if (!response.ok) {
|
||||
if (response.status === 409)
|
||||
alert(await response.text());
|
||||
$("renameButton").disabled = false;
|
||||
document.getElementById("renameButton").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("renameButton").click();
|
||||
document.getElementById("renameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -34,17 +34,17 @@
|
|||
const isFolder = ((searchParams.get("isFolder")) === "true");
|
||||
|
||||
const oldName = window.qBittorrent.Filesystem.fileName(oldPath);
|
||||
$("rename").value = oldName;
|
||||
$("rename").focus();
|
||||
document.getElementById("rename").value = oldName;
|
||||
document.getElementById("rename").focus();
|
||||
if (!isFolder)
|
||||
$("rename").setSelectionRange(0, oldName.lastIndexOf("."));
|
||||
document.getElementById("rename").setSelectionRange(0, oldName.lastIndexOf("."));
|
||||
|
||||
$("renameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("renameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const newName = $("rename").value.trim();
|
||||
const newName = document.getElementById("rename").value.trim();
|
||||
if (newName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
|
@ -55,7 +55,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$("renameButton").disabled = true;
|
||||
document.getElementById("renameButton").disabled = true;
|
||||
|
||||
const parentPath = window.qBittorrent.Filesystem.folderName(oldPath);
|
||||
const newPath = parentPath
|
||||
|
@ -72,7 +72,7 @@
|
|||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
alert("QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]");
|
||||
$("renameButton").disabled = false;
|
||||
document.getElementById("renameButton").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,50 +80,50 @@
|
|||
|
||||
// Load Multi Rename Preferences
|
||||
const multiRenamePrefChecked = LocalPreferences.get("multirename_rememberPreferences", "true") === "true";
|
||||
$("multirename_rememberprefs_checkbox").checked = multiRenamePrefChecked;
|
||||
document.getElementById("multirename_rememberprefs_checkbox").checked = multiRenamePrefChecked;
|
||||
|
||||
if (multiRenamePrefChecked) {
|
||||
const multirename_search = LocalPreferences.get("multirename_search", "");
|
||||
fileRenamer.setSearch(multirename_search);
|
||||
$("multiRenameSearch").value = multirename_search;
|
||||
document.getElementById("multiRenameSearch").value = multirename_search;
|
||||
|
||||
const multirename_useRegex = LocalPreferences.get("multirename_useRegex", false);
|
||||
fileRenamer.useRegex = multirename_useRegex === "true";
|
||||
$("use_regex_search").checked = fileRenamer.useRegex;
|
||||
document.getElementById("use_regex_search").checked = fileRenamer.useRegex;
|
||||
|
||||
const multirename_matchAllOccurrences = LocalPreferences.get("multirename_matchAllOccurrences", false);
|
||||
fileRenamer.matchAllOccurrences = multirename_matchAllOccurrences === "true";
|
||||
$("match_all_occurrences").checked = fileRenamer.matchAllOccurrences;
|
||||
document.getElementById("match_all_occurrences").checked = fileRenamer.matchAllOccurrences;
|
||||
|
||||
const multirename_caseSensitive = LocalPreferences.get("multirename_caseSensitive", false);
|
||||
fileRenamer.caseSensitive = multirename_caseSensitive === "true";
|
||||
$("case_sensitive").checked = fileRenamer.caseSensitive;
|
||||
document.getElementById("case_sensitive").checked = fileRenamer.caseSensitive;
|
||||
|
||||
const multirename_replace = LocalPreferences.get("multirename_replace", "");
|
||||
fileRenamer.setReplacement(multirename_replace);
|
||||
$("multiRenameReplace").value = multirename_replace;
|
||||
document.getElementById("multiRenameReplace").value = multirename_replace;
|
||||
|
||||
const multirename_appliesTo = LocalPreferences.get("multirename_appliesTo", window.qBittorrent.MultiRename.AppliesTo.FilenameExtension);
|
||||
fileRenamer.appliesTo = window.qBittorrent.MultiRename.AppliesTo[multirename_appliesTo];
|
||||
$("applies_to_option").value = fileRenamer.appliesTo;
|
||||
document.getElementById("applies_to_option").value = fileRenamer.appliesTo;
|
||||
|
||||
const multirename_includeFiles = LocalPreferences.get("multirename_includeFiles", true);
|
||||
fileRenamer.includeFiles = multirename_includeFiles === "true";
|
||||
$("include_files").checked = fileRenamer.includeFiles;
|
||||
document.getElementById("include_files").checked = fileRenamer.includeFiles;
|
||||
|
||||
const multirename_includeFolders = LocalPreferences.get("multirename_includeFolders", false);
|
||||
fileRenamer.includeFolders = multirename_includeFolders === "true";
|
||||
$("include_folders").checked = fileRenamer.includeFolders;
|
||||
document.getElementById("include_folders").checked = fileRenamer.includeFolders;
|
||||
|
||||
const multirename_fileEnumerationStart = LocalPreferences.get("multirename_fileEnumerationStart", 0);
|
||||
fileRenamer.fileEnumerationStart = Number(multirename_fileEnumerationStart);
|
||||
$("file_counter").value = fileRenamer.fileEnumerationStart;
|
||||
document.getElementById("file_counter").value = fileRenamer.fileEnumerationStart;
|
||||
|
||||
const multirename_replaceAll = LocalPreferences.get("multirename_replaceAll", false);
|
||||
fileRenamer.replaceAll = multirename_replaceAll === "true";
|
||||
const renameButtonValue = fileRenamer.replaceAll ? "Replace All" : "Replace";
|
||||
$("renameOptions").value = renameButtonValue;
|
||||
$("renameButton").value = renameButtonValue;
|
||||
document.getElementById("renameOptions").value = renameButtonValue;
|
||||
document.getElementById("renameButton").value = renameButtonValue;
|
||||
}
|
||||
|
||||
// Fires every time a row's selection changes
|
||||
|
@ -133,26 +133,26 @@
|
|||
};
|
||||
|
||||
// Setup Search Events that control renaming
|
||||
$("multiRenameSearch").addEventListener("input", (e) => {
|
||||
document.getElementById("multiRenameSearch").addEventListener("input", (e) => {
|
||||
const sanitized = e.target.value.replace(/\n/g, "");
|
||||
$("multiRenameSearch").value = sanitized;
|
||||
document.getElementById("multiRenameSearch").value = sanitized;
|
||||
|
||||
// Search input has changed
|
||||
$("multiRenameSearch").style["border-color"] = "";
|
||||
document.getElementById("multiRenameSearch").style["border-color"] = "";
|
||||
LocalPreferences.set("multirename_search", sanitized);
|
||||
fileRenamer.setSearch(sanitized);
|
||||
});
|
||||
$("use_regex_search").addEventListener("change", (e) => {
|
||||
document.getElementById("use_regex_search").addEventListener("change", (e) => {
|
||||
fileRenamer.useRegex = e.target.checked;
|
||||
LocalPreferences.set("multirename_useRegex", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$("match_all_occurrences").addEventListener("change", (e) => {
|
||||
document.getElementById("match_all_occurrences").addEventListener("change", (e) => {
|
||||
fileRenamer.matchAllOccurrences = e.target.checked;
|
||||
LocalPreferences.set("multirename_matchAllOccurrences", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$("case_sensitive").addEventListener("change", (e) => {
|
||||
document.getElementById("case_sensitive").addEventListener("change", (e) => {
|
||||
fileRenamer.caseSensitive = e.target.checked;
|
||||
LocalPreferences.set("multirename_caseSensitive", e.target.checked);
|
||||
fileRenamer.update();
|
||||
|
@ -179,35 +179,35 @@
|
|||
}
|
||||
};
|
||||
fileRenamer.onInvalidRegex = (err) => {
|
||||
$("multiRenameSearch").style["border-color"] = "#CC0033";
|
||||
document.getElementById("multiRenameSearch").style["border-color"] = "#CC0033";
|
||||
};
|
||||
|
||||
// Setup Replace Events that control renaming
|
||||
$("multiRenameReplace").addEventListener("input", (e) => {
|
||||
document.getElementById("multiRenameReplace").addEventListener("input", (e) => {
|
||||
const sanitized = e.target.value.replace(/\n/g, "");
|
||||
$("multiRenameReplace").value = sanitized;
|
||||
document.getElementById("multiRenameReplace").value = sanitized;
|
||||
|
||||
// Replace input has changed
|
||||
$("multiRenameReplace").style["border-color"] = "";
|
||||
document.getElementById("multiRenameReplace").style["border-color"] = "";
|
||||
LocalPreferences.set("multirename_replace", sanitized);
|
||||
fileRenamer.setReplacement(sanitized);
|
||||
});
|
||||
$("applies_to_option").addEventListener("change", (e) => {
|
||||
document.getElementById("applies_to_option").addEventListener("change", (e) => {
|
||||
fileRenamer.appliesTo = e.target.value;
|
||||
LocalPreferences.set("multirename_appliesTo", e.target.value);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$("include_files").addEventListener("change", (e) => {
|
||||
document.getElementById("include_files").addEventListener("change", (e) => {
|
||||
fileRenamer.includeFiles = e.target.checked;
|
||||
LocalPreferences.set("multirename_includeFiles", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$("include_folders").addEventListener("change", (e) => {
|
||||
document.getElementById("include_folders").addEventListener("change", (e) => {
|
||||
fileRenamer.includeFolders = e.target.checked;
|
||||
LocalPreferences.set("multirename_includeFolders", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$("file_counter").addEventListener("input", (e) => {
|
||||
document.getElementById("file_counter").addEventListener("input", (e) => {
|
||||
let value = e.target.valueAsNumber;
|
||||
if (!value)
|
||||
value = 0;
|
||||
|
@ -216,46 +216,46 @@
|
|||
if (value > 99999999)
|
||||
value = 99999999;
|
||||
fileRenamer.fileEnumerationStart = value;
|
||||
$("file_counter").value = value;
|
||||
document.getElementById("file_counter").value = value;
|
||||
LocalPreferences.set("multirename_fileEnumerationStart", value);
|
||||
fileRenamer.update();
|
||||
});
|
||||
|
||||
// Setup Rename Operation Events
|
||||
$("renameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("renameButton").addEventListener("click", (e) => {
|
||||
// Disable Search Options
|
||||
$("multiRenameSearch").disabled = true;
|
||||
$("use_regex_search").disabled = true;
|
||||
$("match_all_occurrences").disabled = true;
|
||||
$("case_sensitive").disabled = true;
|
||||
document.getElementById("multiRenameSearch").disabled = true;
|
||||
document.getElementById("use_regex_search").disabled = true;
|
||||
document.getElementById("match_all_occurrences").disabled = true;
|
||||
document.getElementById("case_sensitive").disabled = true;
|
||||
// Disable Replace Options
|
||||
$("multiRenameReplace").disabled = true;
|
||||
$("applies_to_option").disabled = true;
|
||||
$("include_files").disabled = true;
|
||||
$("include_folders").disabled = true;
|
||||
$("file_counter").disabled = true;
|
||||
document.getElementById("multiRenameReplace").disabled = true;
|
||||
document.getElementById("applies_to_option").disabled = true;
|
||||
document.getElementById("include_files").disabled = true;
|
||||
document.getElementById("include_folders").disabled = true;
|
||||
document.getElementById("file_counter").disabled = true;
|
||||
// Disable Rename Buttons
|
||||
$("renameButton").disabled = true;
|
||||
$("renameOptions").disabled = true;
|
||||
document.getElementById("renameButton").disabled = true;
|
||||
document.getElementById("renameOptions").disabled = true;
|
||||
// Clear error text
|
||||
$("rename_error").textContent = "";
|
||||
document.getElementById("rename_error").textContent = "";
|
||||
fileRenamer.rename();
|
||||
});
|
||||
fileRenamer.onRenamed = (rows) => {
|
||||
// Disable Search Options
|
||||
$("multiRenameSearch").disabled = false;
|
||||
$("use_regex_search").disabled = false;
|
||||
$("match_all_occurrences").disabled = false;
|
||||
$("case_sensitive").disabled = false;
|
||||
document.getElementById("multiRenameSearch").disabled = false;
|
||||
document.getElementById("use_regex_search").disabled = false;
|
||||
document.getElementById("match_all_occurrences").disabled = false;
|
||||
document.getElementById("case_sensitive").disabled = false;
|
||||
// Disable Replace Options
|
||||
$("multiRenameReplace").disabled = false;
|
||||
$("applies_to_option").disabled = false;
|
||||
$("include_files").disabled = false;
|
||||
$("include_folders").disabled = false;
|
||||
$("file_counter").disabled = false;
|
||||
document.getElementById("multiRenameReplace").disabled = false;
|
||||
document.getElementById("applies_to_option").disabled = false;
|
||||
document.getElementById("include_files").disabled = false;
|
||||
document.getElementById("include_folders").disabled = false;
|
||||
document.getElementById("file_counter").disabled = false;
|
||||
// Disable Rename Buttons
|
||||
$("renameButton").disabled = false;
|
||||
$("renameOptions").disabled = false;
|
||||
document.getElementById("renameButton").disabled = false;
|
||||
document.getElementById("renameOptions").disabled = false;
|
||||
|
||||
// Recreate table
|
||||
let selectedRows = bulkRenameFilesTable.getSelectedRows().map(row => row.rowId.toString());
|
||||
|
@ -266,15 +266,15 @@
|
|||
// Adjust file enumeration count by 1 when replacing single files to prevent naming conflicts
|
||||
if (!fileRenamer.replaceAll) {
|
||||
fileRenamer.fileEnumerationStart++;
|
||||
$("file_counter").value = fileRenamer.fileEnumerationStart;
|
||||
document.getElementById("file_counter").value = fileRenamer.fileEnumerationStart;
|
||||
}
|
||||
setupTable(selectedRows);
|
||||
};
|
||||
fileRenamer.onRenameError = (response, row) => {
|
||||
if (response.status === 409)
|
||||
$("rename_error").textContent = `QBT_TR(Rename failed: file or folder already exists)QBT_TR[CONTEXT=PropertiesWidget] \`${row.renamed}\``;
|
||||
document.getElementById("rename_error").textContent = `QBT_TR(Rename failed: file or folder already exists)QBT_TR[CONTEXT=PropertiesWidget] \`${row.renamed}\``;
|
||||
};
|
||||
$("renameOptions").addEventListener("change", (e) => {
|
||||
document.getElementById("renameOptions").addEventListener("change", (e) => {
|
||||
const combobox = e.target;
|
||||
const replaceOperation = combobox.value;
|
||||
if (replaceOperation === "Replace")
|
||||
|
@ -284,9 +284,9 @@
|
|||
else
|
||||
fileRenamer.replaceAll = false;
|
||||
LocalPreferences.set("multirename_replaceAll", fileRenamer.replaceAll);
|
||||
$("renameButton").value = replaceOperation;
|
||||
document.getElementById("renameButton").value = replaceOperation;
|
||||
});
|
||||
$("closeButton").addEventListener("click", (event) => {
|
||||
document.getElementById("closeButton").addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
window.qBittorrent.Client.closeWindow(windowEl);
|
||||
});
|
||||
|
@ -409,7 +409,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>
|
||||
|
@ -423,7 +423,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>
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("renameButton").click();
|
||||
document.getElementById("renameButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -29,16 +29,16 @@
|
|||
|
||||
const oldName = new URLSearchParams(window.location.search).get("rule");
|
||||
|
||||
$("rename").value = oldName;
|
||||
$("rename").focus();
|
||||
$("rename").setSelectionRange(0, oldName.length);
|
||||
document.getElementById("rename").value = oldName;
|
||||
document.getElementById("rename").focus();
|
||||
document.getElementById("rename").setSelectionRange(0, oldName.length);
|
||||
|
||||
$("renameButton").addEventListener("click", (e) => {
|
||||
document.getElementById("renameButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const newName = $("rename").value.trim();
|
||||
const newName = document.getElementById("rename").value.trim();
|
||||
if (newName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
|
@ -49,7 +49,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$("renameButton").disabled = true;
|
||||
document.getElementById("renameButton").disabled = true;
|
||||
|
||||
fetch("api/v2/rss/renameRule", {
|
||||
method: "POST",
|
||||
|
|
|
@ -53,7 +53,7 @@ window.qBittorrent.Cache ??= (() => {
|
|||
class BuildInfoCache {
|
||||
#m_store = {};
|
||||
|
||||
async init() {
|
||||
init() {
|
||||
return fetch("api/v2/app/buildInfo", {
|
||||
method: "GET",
|
||||
cache: "no-store"
|
||||
|
@ -80,7 +80,7 @@ window.qBittorrent.Cache ??= (() => {
|
|||
// onFailure: () => {},
|
||||
// onSuccess: () => {}
|
||||
// }
|
||||
async init(obj = {}) {
|
||||
init(obj = {}) {
|
||||
return fetch("api/v2/app/preferences", {
|
||||
method: "GET",
|
||||
cache: "no-store"
|
||||
|
@ -153,7 +153,7 @@ window.qBittorrent.Cache ??= (() => {
|
|||
class QbtVersionCache {
|
||||
#m_store = "";
|
||||
|
||||
async init() {
|
||||
init() {
|
||||
return fetch("api/v2/app/version", {
|
||||
method: "GET",
|
||||
cache: "no-store"
|
||||
|
|
|
@ -174,21 +174,23 @@ 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;
|
||||
|
||||
const saveColumnSizes = () => {
|
||||
const filters_width = $("Filters").getSize().x;
|
||||
const filters_width = document.getElementById("Filters").getSize().x;
|
||||
LocalPreferences.set("filters_width", filters_width);
|
||||
const properties_height_rel = $("propertiesPanel").getSize().y / Window.getSize().y;
|
||||
const properties_height_rel = document.getElementById("propertiesPanel").getSize().y / Window.getSize().y;
|
||||
LocalPreferences.set("properties_height_rel", properties_height_rel);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", window.qBittorrent.Misc.createDebounceHandler(500, (e) => {
|
||||
// only save sizes if the columns are visible
|
||||
if (!$("mainColumn").classList.contains("invisible"))
|
||||
if (!document.getElementById("mainColumn").classList.contains("invisible"))
|
||||
saveColumnSizes();
|
||||
}));
|
||||
|
||||
|
@ -221,7 +223,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
|
||||
// start off hidden
|
||||
$("searchTabColumn").classList.add("invisible");
|
||||
document.getElementById("searchTabColumn").classList.add("invisible");
|
||||
};
|
||||
|
||||
const buildRssTab = () => {
|
||||
|
@ -232,7 +234,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
|
||||
// start off hidden
|
||||
$("rssTabColumn").classList.add("invisible");
|
||||
document.getElementById("rssTabColumn").classList.add("invisible");
|
||||
};
|
||||
|
||||
const buildLogTab = () => {
|
||||
|
@ -243,7 +245,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
|
||||
// start off hidden
|
||||
$("logTabColumn").classList.add("invisible");
|
||||
document.getElementById("logTabColumn").classList.add("invisible");
|
||||
};
|
||||
|
||||
buildTransfersTab();
|
||||
|
@ -342,28 +344,28 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
// Show Top Toolbar is enabled by default
|
||||
let showTopToolbar = LocalPreferences.get("show_top_toolbar", "true") === "true";
|
||||
if (!showTopToolbar) {
|
||||
$("showTopToolbarLink").firstElementChild.style.opacity = "0";
|
||||
$("mochaToolbar").classList.add("invisible");
|
||||
document.getElementById("showTopToolbarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("mochaToolbar").classList.add("invisible");
|
||||
}
|
||||
|
||||
// Show Status Bar is enabled by default
|
||||
let showStatusBar = LocalPreferences.get("show_status_bar", "true") === "true";
|
||||
if (!showStatusBar) {
|
||||
$("showStatusBarLink").firstElementChild.style.opacity = "0";
|
||||
$("desktopFooterWrapper").classList.add("invisible");
|
||||
document.getElementById("showStatusBarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("desktopFooterWrapper").classList.add("invisible");
|
||||
}
|
||||
|
||||
// Show Filters Sidebar is enabled by default
|
||||
let showFiltersSidebar = LocalPreferences.get("show_filters_sidebar", "true") === "true";
|
||||
if (!showFiltersSidebar) {
|
||||
$("showFiltersSidebarLink").firstElementChild.style.opacity = "0";
|
||||
$("filtersColumn").classList.add("invisible");
|
||||
$("filtersColumn_handle").classList.add("invisible");
|
||||
document.getElementById("showFiltersSidebarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("filtersColumn").classList.add("invisible");
|
||||
document.getElementById("filtersColumn_handle").classList.add("invisible");
|
||||
}
|
||||
|
||||
let speedInTitle = LocalPreferences.get("speed_in_browser_title_bar") === "true";
|
||||
if (!speedInTitle)
|
||||
$("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "0";
|
||||
|
||||
// After showing/hiding the toolbar + status bar
|
||||
window.qBittorrent.Client.showSearchEngine(LocalPreferences.get("show_search_engine") !== "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");
|
||||
|
||||
|
@ -614,11 +616,11 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
};
|
||||
|
||||
const updateTagList = () => {
|
||||
const tagFilterList = $("tagFilterList");
|
||||
const tagFilterList = document.getElementById("tagFilterList");
|
||||
if (tagFilterList === null)
|
||||
return;
|
||||
|
||||
[...tagFilterList.children].forEach((el) => { el.destroy(); });
|
||||
[...tagFilterList.children].forEach((el) => { el.remove(); });
|
||||
|
||||
const tagItemTemplate = document.getElementById("tagFilterItem");
|
||||
|
||||
|
@ -667,11 +669,11 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
};
|
||||
|
||||
const updateTrackerList = () => {
|
||||
const trackerFilterList = $("trackerFilterList");
|
||||
const trackerFilterList = document.getElementById("trackerFilterList");
|
||||
if (trackerFilterList === null)
|
||||
return;
|
||||
|
||||
[...trackerFilterList.children].forEach((el) => { el.destroy(); });
|
||||
[...trackerFilterList.children].forEach((el) => { el.remove(); });
|
||||
|
||||
const trackerItemTemplate = document.getElementById("trackerFilterItem");
|
||||
|
||||
|
@ -763,7 +765,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
})
|
||||
.then(async (response) => {
|
||||
if (response.ok) {
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
|
||||
const responseJSON = await response.json();
|
||||
|
||||
|
@ -933,7 +935,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
syncData(window.qBittorrent.Client.getSyncMainDataInterval());
|
||||
},
|
||||
(error) => {
|
||||
const errorDiv = $("error_div");
|
||||
const errorDiv = document.getElementById("error_div");
|
||||
if (errorDiv)
|
||||
errorDiv.textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
syncRequestInProgress = false;
|
||||
|
@ -964,12 +966,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
if (serverState.dl_rate_limit > 0)
|
||||
transfer_info += ` [${window.qBittorrent.Misc.friendlyUnit(serverState.dl_rate_limit, true)}]`;
|
||||
transfer_info += ` (${window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_data, false)})`;
|
||||
$("DlInfos").textContent = transfer_info;
|
||||
document.getElementById("DlInfos").textContent = transfer_info;
|
||||
transfer_info = window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true);
|
||||
if (serverState.up_rate_limit > 0)
|
||||
transfer_info += ` [${window.qBittorrent.Misc.friendlyUnit(serverState.up_rate_limit, true)}]`;
|
||||
transfer_info += ` (${window.qBittorrent.Misc.friendlyUnit(serverState.up_info_data, false)})`;
|
||||
$("UpInfos").textContent = transfer_info;
|
||||
document.getElementById("UpInfos").textContent = transfer_info;
|
||||
|
||||
document.title = (speedInTitle
|
||||
? (`QBT_TR([D: %1, U: %2])QBT_TR[CONTEXT=MainWindow] `
|
||||
|
@ -978,7 +980,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
: "")
|
||||
+ window.qBittorrent.Client.mainTitle();
|
||||
|
||||
$("freeSpaceOnDisk").textContent = "QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk));
|
||||
document.getElementById("freeSpaceOnDisk").textContent = "QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk));
|
||||
|
||||
const externalIPsElement = document.getElementById("externalIPs");
|
||||
if (window.qBittorrent.Cache.preferences.get().status_bar_external_ip) {
|
||||
|
@ -991,9 +993,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");
|
||||
}
|
||||
|
@ -1015,35 +1017,35 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
// Statistics dialog
|
||||
if (document.getElementById("statisticsContent")) {
|
||||
$("AlltimeDL").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.alltime_dl, false);
|
||||
$("AlltimeUL").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.alltime_ul, false);
|
||||
$("TotalWastedSession").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_wasted_session, false);
|
||||
$("GlobalRatio").textContent = serverState.global_ratio;
|
||||
$("TotalPeerConnections").textContent = serverState.total_peer_connections;
|
||||
$("ReadCacheHits").textContent = `${serverState.read_cache_hits}%`;
|
||||
$("TotalBuffersSize").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_buffers_size, false);
|
||||
$("WriteCacheOverload").textContent = `${serverState.write_cache_overload}%`;
|
||||
$("ReadCacheOverload").textContent = `${serverState.read_cache_overload}%`;
|
||||
$("QueuedIOJobs").textContent = serverState.queued_io_jobs;
|
||||
$("AverageTimeInQueue").textContent = `${serverState.average_time_queue} ms`;
|
||||
$("TotalQueuedSize").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_queued_size, false);
|
||||
document.getElementById("AlltimeDL").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.alltime_dl, false);
|
||||
document.getElementById("AlltimeUL").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.alltime_ul, false);
|
||||
document.getElementById("TotalWastedSession").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_wasted_session, false);
|
||||
document.getElementById("GlobalRatio").textContent = serverState.global_ratio;
|
||||
document.getElementById("TotalPeerConnections").textContent = serverState.total_peer_connections;
|
||||
document.getElementById("ReadCacheHits").textContent = `${serverState.read_cache_hits}%`;
|
||||
document.getElementById("TotalBuffersSize").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_buffers_size, false);
|
||||
document.getElementById("WriteCacheOverload").textContent = `${serverState.write_cache_overload}%`;
|
||||
document.getElementById("ReadCacheOverload").textContent = `${serverState.read_cache_overload}%`;
|
||||
document.getElementById("QueuedIOJobs").textContent = serverState.queued_io_jobs;
|
||||
document.getElementById("AverageTimeInQueue").textContent = `${serverState.average_time_queue} ms`;
|
||||
document.getElementById("TotalQueuedSize").textContent = window.qBittorrent.Misc.friendlyUnit(serverState.total_queued_size, false);
|
||||
}
|
||||
|
||||
switch (serverState.connection_status) {
|
||||
case "connected":
|
||||
$("connectionStatus").src = "images/connected.svg";
|
||||
$("connectionStatus").alt = "QBT_TR(Connection status: Connected)QBT_TR[CONTEXT=MainWindow]";
|
||||
$("connectionStatus").title = "QBT_TR(Connection status: Connected)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").src = "images/connected.svg";
|
||||
document.getElementById("connectionStatus").alt = "QBT_TR(Connection status: Connected)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").title = "QBT_TR(Connection status: Connected)QBT_TR[CONTEXT=MainWindow]";
|
||||
break;
|
||||
case "firewalled":
|
||||
$("connectionStatus").src = "images/firewalled.svg";
|
||||
$("connectionStatus").alt = "QBT_TR(Connection status: Firewalled)QBT_TR[CONTEXT=MainWindow]";
|
||||
$("connectionStatus").title = "QBT_TR(Connection status: Firewalled)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").src = "images/firewalled.svg";
|
||||
document.getElementById("connectionStatus").alt = "QBT_TR(Connection status: Firewalled)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").title = "QBT_TR(Connection status: Firewalled)QBT_TR[CONTEXT=MainWindow]";
|
||||
break;
|
||||
default:
|
||||
$("connectionStatus").src = "images/disconnected.svg";
|
||||
$("connectionStatus").alt = "QBT_TR(Connection status: Disconnected)QBT_TR[CONTEXT=MainWindow]";
|
||||
$("connectionStatus").title = "QBT_TR(Connection status: Disconnected)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").src = "images/disconnected.svg";
|
||||
document.getElementById("connectionStatus").alt = "QBT_TR(Connection status: Disconnected)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("connectionStatus").title = "QBT_TR(Connection status: Disconnected)QBT_TR[CONTEXT=MainWindow]";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1052,20 +1054,20 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
torrentsTable.columns["priority"].force_hide = !queueing_enabled;
|
||||
torrentsTable.updateColumn("priority");
|
||||
if (queueing_enabled) {
|
||||
$("topQueuePosItem").classList.remove("invisible");
|
||||
$("increaseQueuePosItem").classList.remove("invisible");
|
||||
$("decreaseQueuePosItem").classList.remove("invisible");
|
||||
$("bottomQueuePosItem").classList.remove("invisible");
|
||||
$("queueingButtons").classList.remove("invisible");
|
||||
$("queueingMenuItems").classList.remove("invisible");
|
||||
document.getElementById("topQueuePosItem").classList.remove("invisible");
|
||||
document.getElementById("increaseQueuePosItem").classList.remove("invisible");
|
||||
document.getElementById("decreaseQueuePosItem").classList.remove("invisible");
|
||||
document.getElementById("bottomQueuePosItem").classList.remove("invisible");
|
||||
document.getElementById("queueingButtons").classList.remove("invisible");
|
||||
document.getElementById("queueingMenuItems").classList.remove("invisible");
|
||||
}
|
||||
else {
|
||||
$("topQueuePosItem").classList.add("invisible");
|
||||
$("increaseQueuePosItem").classList.add("invisible");
|
||||
$("decreaseQueuePosItem").classList.add("invisible");
|
||||
$("bottomQueuePosItem").classList.add("invisible");
|
||||
$("queueingButtons").classList.add("invisible");
|
||||
$("queueingMenuItems").classList.add("invisible");
|
||||
document.getElementById("topQueuePosItem").classList.add("invisible");
|
||||
document.getElementById("increaseQueuePosItem").classList.add("invisible");
|
||||
document.getElementById("decreaseQueuePosItem").classList.add("invisible");
|
||||
document.getElementById("bottomQueuePosItem").classList.add("invisible");
|
||||
document.getElementById("queueingButtons").classList.add("invisible");
|
||||
document.getElementById("queueingMenuItems").classList.add("invisible");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1084,18 +1086,18 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
const updateAltSpeedIcon = (enabled) => {
|
||||
if (enabled) {
|
||||
$("alternativeSpeedLimits").src = "images/slow.svg";
|
||||
$("alternativeSpeedLimits").alt = "QBT_TR(Alternative speed limits: On)QBT_TR[CONTEXT=MainWindow]";
|
||||
$("alternativeSpeedLimits").title = "QBT_TR(Alternative speed limits: On)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("alternativeSpeedLimits").src = "images/slow.svg";
|
||||
document.getElementById("alternativeSpeedLimits").alt = "QBT_TR(Alternative speed limits: On)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("alternativeSpeedLimits").title = "QBT_TR(Alternative speed limits: On)QBT_TR[CONTEXT=MainWindow]";
|
||||
}
|
||||
else {
|
||||
$("alternativeSpeedLimits").src = "images/slow_off.svg";
|
||||
$("alternativeSpeedLimits").alt = "QBT_TR(Alternative speed limits: Off)QBT_TR[CONTEXT=MainWindow]";
|
||||
$("alternativeSpeedLimits").title = "QBT_TR(Alternative speed limits: Off)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("alternativeSpeedLimits").src = "images/slow_off.svg";
|
||||
document.getElementById("alternativeSpeedLimits").alt = "QBT_TR(Alternative speed limits: Off)QBT_TR[CONTEXT=MainWindow]";
|
||||
document.getElementById("alternativeSpeedLimits").title = "QBT_TR(Alternative speed limits: Off)QBT_TR[CONTEXT=MainWindow]";
|
||||
}
|
||||
};
|
||||
|
||||
$("alternativeSpeedLimits").addEventListener("click", () => {
|
||||
document.getElementById("alternativeSpeedLimits").addEventListener("click", (event) => {
|
||||
// Change icon immediately to give some feedback
|
||||
updateAltSpeedIcon(!alternativeSpeedLimits);
|
||||
|
||||
|
@ -1114,33 +1116,33 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
});
|
||||
|
||||
$("DlInfos").addEventListener("click", () => { globalDownloadLimitFN(); });
|
||||
$("UpInfos").addEventListener("click", () => { globalUploadLimitFN(); });
|
||||
document.getElementById("DlInfos").addEventListener("click", (event) => { globalDownloadLimitFN(); });
|
||||
document.getElementById("UpInfos").addEventListener("click", (event) => { globalUploadLimitFN(); });
|
||||
|
||||
$("showTopToolbarLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showTopToolbarLink").addEventListener("click", (e) => {
|
||||
showTopToolbar = !showTopToolbar;
|
||||
LocalPreferences.set("show_top_toolbar", showTopToolbar.toString());
|
||||
if (showTopToolbar) {
|
||||
$("showTopToolbarLink").firstElementChild.style.opacity = "1";
|
||||
$("mochaToolbar").classList.remove("invisible");
|
||||
document.getElementById("showTopToolbarLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("mochaToolbar").classList.remove("invisible");
|
||||
}
|
||||
else {
|
||||
$("showTopToolbarLink").firstElementChild.style.opacity = "0";
|
||||
$("mochaToolbar").classList.add("invisible");
|
||||
document.getElementById("showTopToolbarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("mochaToolbar").classList.add("invisible");
|
||||
}
|
||||
MochaUI.Desktop.setDesktopSize();
|
||||
});
|
||||
|
||||
$("showStatusBarLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showStatusBarLink").addEventListener("click", (e) => {
|
||||
showStatusBar = !showStatusBar;
|
||||
LocalPreferences.set("show_status_bar", showStatusBar.toString());
|
||||
if (showStatusBar) {
|
||||
$("showStatusBarLink").firstElementChild.style.opacity = "1";
|
||||
$("desktopFooterWrapper").classList.remove("invisible");
|
||||
document.getElementById("showStatusBarLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("desktopFooterWrapper").classList.remove("invisible");
|
||||
}
|
||||
else {
|
||||
$("showStatusBarLink").firstElementChild.style.opacity = "0";
|
||||
$("desktopFooterWrapper").classList.add("invisible");
|
||||
document.getElementById("showStatusBarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("desktopFooterWrapper").classList.add("invisible");
|
||||
}
|
||||
MochaUI.Desktop.setDesktopSize();
|
||||
});
|
||||
|
@ -1164,49 +1166,49 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
navigator.registerProtocolHandler("magnet", templateUrl,
|
||||
"qBittorrent WebUI magnet handler");
|
||||
};
|
||||
$("registerMagnetHandlerLink").addEventListener("click", (e) => {
|
||||
document.getElementById("registerMagnetHandlerLink").addEventListener("click", (e) => {
|
||||
registerMagnetHandler();
|
||||
});
|
||||
|
||||
$("showFiltersSidebarLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showFiltersSidebarLink").addEventListener("click", (e) => {
|
||||
showFiltersSidebar = !showFiltersSidebar;
|
||||
LocalPreferences.set("show_filters_sidebar", showFiltersSidebar.toString());
|
||||
if (showFiltersSidebar) {
|
||||
$("showFiltersSidebarLink").firstElementChild.style.opacity = "1";
|
||||
$("filtersColumn").classList.remove("invisible");
|
||||
$("filtersColumn_handle").classList.remove("invisible");
|
||||
document.getElementById("showFiltersSidebarLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("filtersColumn").classList.remove("invisible");
|
||||
document.getElementById("filtersColumn_handle").classList.remove("invisible");
|
||||
}
|
||||
else {
|
||||
$("showFiltersSidebarLink").firstElementChild.style.opacity = "0";
|
||||
$("filtersColumn").classList.add("invisible");
|
||||
$("filtersColumn_handle").classList.add("invisible");
|
||||
document.getElementById("showFiltersSidebarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("filtersColumn").classList.add("invisible");
|
||||
document.getElementById("filtersColumn_handle").classList.add("invisible");
|
||||
}
|
||||
MochaUI.Desktop.setDesktopSize();
|
||||
});
|
||||
|
||||
$("speedInBrowserTitleBarLink").addEventListener("click", (e) => {
|
||||
document.getElementById("speedInBrowserTitleBarLink").addEventListener("click", (e) => {
|
||||
speedInTitle = !speedInTitle;
|
||||
LocalPreferences.set("speed_in_browser_title_bar", speedInTitle.toString());
|
||||
if (speedInTitle)
|
||||
$("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "1";
|
||||
else
|
||||
$("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("speedInBrowserTitleBarLink").firstElementChild.style.opacity = "0";
|
||||
processServerState();
|
||||
});
|
||||
|
||||
$("showSearchEngineLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showSearchEngineLink").addEventListener("click", (e) => {
|
||||
window.qBittorrent.Client.showSearchEngine(!window.qBittorrent.Client.isShowSearchEngine());
|
||||
LocalPreferences.set("show_search_engine", window.qBittorrent.Client.isShowSearchEngine().toString());
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
$("showRssReaderLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showRssReaderLink").addEventListener("click", (e) => {
|
||||
window.qBittorrent.Client.showRssReader(!window.qBittorrent.Client.isShowRssReader());
|
||||
LocalPreferences.set("show_rss_reader", window.qBittorrent.Client.isShowRssReader().toString());
|
||||
updateTabDisplay();
|
||||
});
|
||||
|
||||
$("showLogViewerLink").addEventListener("click", (e) => {
|
||||
document.getElementById("showLogViewerLink").addEventListener("click", (e) => {
|
||||
window.qBittorrent.Client.showLogViewer(!window.qBittorrent.Client.isShowLogViewer());
|
||||
LocalPreferences.set("show_log_viewer", window.qBittorrent.Client.isShowLogViewer().toString());
|
||||
updateTabDisplay();
|
||||
|
@ -1214,64 +1216,64 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
const updateTabDisplay = () => {
|
||||
if (window.qBittorrent.Client.isShowRssReader()) {
|
||||
$("showRssReaderLink").firstElementChild.style.opacity = "1";
|
||||
$("mainWindowTabs").classList.remove("invisible");
|
||||
$("rssTabLink").classList.remove("invisible");
|
||||
document.getElementById("showRssReaderLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("mainWindowTabs").classList.remove("invisible");
|
||||
document.getElementById("rssTabLink").classList.remove("invisible");
|
||||
if (!MochaUI.Panels.instances.RssPanel)
|
||||
addRssPanel();
|
||||
}
|
||||
else {
|
||||
$("showRssReaderLink").firstElementChild.style.opacity = "0";
|
||||
$("rssTabLink").classList.add("invisible");
|
||||
if ($("rssTabLink").classList.contains("selected"))
|
||||
$("transfersTabLink").click();
|
||||
document.getElementById("showRssReaderLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("rssTabLink").classList.add("invisible");
|
||||
if (document.getElementById("rssTabLink").classList.contains("selected"))
|
||||
document.getElementById("transfersTabLink").click();
|
||||
}
|
||||
|
||||
if (window.qBittorrent.Client.isShowSearchEngine()) {
|
||||
$("showSearchEngineLink").firstElementChild.style.opacity = "1";
|
||||
$("mainWindowTabs").classList.remove("invisible");
|
||||
$("searchTabLink").classList.remove("invisible");
|
||||
document.getElementById("showSearchEngineLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("mainWindowTabs").classList.remove("invisible");
|
||||
document.getElementById("searchTabLink").classList.remove("invisible");
|
||||
if (!MochaUI.Panels.instances.SearchPanel)
|
||||
addSearchPanel();
|
||||
}
|
||||
else {
|
||||
$("showSearchEngineLink").firstElementChild.style.opacity = "0";
|
||||
$("searchTabLink").classList.add("invisible");
|
||||
if ($("searchTabLink").classList.contains("selected"))
|
||||
$("transfersTabLink").click();
|
||||
document.getElementById("showSearchEngineLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("searchTabLink").classList.add("invisible");
|
||||
if (document.getElementById("searchTabLink").classList.contains("selected"))
|
||||
document.getElementById("transfersTabLink").click();
|
||||
}
|
||||
|
||||
if (window.qBittorrent.Client.isShowLogViewer()) {
|
||||
$("showLogViewerLink").firstElementChild.style.opacity = "1";
|
||||
$("mainWindowTabs").classList.remove("invisible");
|
||||
$("logTabLink").classList.remove("invisible");
|
||||
document.getElementById("showLogViewerLink").firstElementChild.style.opacity = "1";
|
||||
document.getElementById("mainWindowTabs").classList.remove("invisible");
|
||||
document.getElementById("logTabLink").classList.remove("invisible");
|
||||
if (!MochaUI.Panels.instances.LogPanel)
|
||||
addLogPanel();
|
||||
}
|
||||
else {
|
||||
$("showLogViewerLink").firstElementChild.style.opacity = "0";
|
||||
$("logTabLink").classList.add("invisible");
|
||||
if ($("logTabLink").classList.contains("selected"))
|
||||
$("transfersTabLink").click();
|
||||
document.getElementById("showLogViewerLink").firstElementChild.style.opacity = "0";
|
||||
document.getElementById("logTabLink").classList.add("invisible");
|
||||
if (document.getElementById("logTabLink").classList.contains("selected"))
|
||||
document.getElementById("transfersTabLink").click();
|
||||
}
|
||||
|
||||
// display no tabs
|
||||
if (!window.qBittorrent.Client.isShowRssReader() && !window.qBittorrent.Client.isShowSearchEngine() && !window.qBittorrent.Client.isShowLogViewer())
|
||||
$("mainWindowTabs").classList.add("invisible");
|
||||
document.getElementById("mainWindowTabs").classList.add("invisible");
|
||||
};
|
||||
|
||||
$("StatisticsLink").addEventListener("click", () => { StatisticsLinkFN(); });
|
||||
document.getElementById("StatisticsLink").addEventListener("click", (event) => { StatisticsLinkFN(); });
|
||||
|
||||
// main window tabs
|
||||
|
||||
const showTransfersTab = () => {
|
||||
const showFiltersSidebar = LocalPreferences.get("show_filters_sidebar", "true") === "true";
|
||||
if (showFiltersSidebar) {
|
||||
$("filtersColumn").classList.remove("invisible");
|
||||
$("filtersColumn_handle").classList.remove("invisible");
|
||||
document.getElementById("filtersColumn").classList.remove("invisible");
|
||||
document.getElementById("filtersColumn_handle").classList.remove("invisible");
|
||||
}
|
||||
$("mainColumn").classList.remove("invisible");
|
||||
$("torrentsFilterToolbar").classList.remove("invisible");
|
||||
document.getElementById("mainColumn").classList.remove("invisible");
|
||||
document.getElementById("torrentsFilterToolbar").classList.remove("invisible");
|
||||
|
||||
customSyncMainDataInterval = null;
|
||||
syncData(100);
|
||||
|
@ -1284,10 +1286,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
};
|
||||
|
||||
const hideTransfersTab = () => {
|
||||
$("filtersColumn").classList.add("invisible");
|
||||
$("filtersColumn_handle").classList.add("invisible");
|
||||
$("mainColumn").classList.add("invisible");
|
||||
$("torrentsFilterToolbar").classList.add("invisible");
|
||||
document.getElementById("filtersColumn").classList.add("invisible");
|
||||
document.getElementById("filtersColumn_handle").classList.add("invisible");
|
||||
document.getElementById("mainColumn").classList.add("invisible");
|
||||
document.getElementById("torrentsFilterToolbar").classList.add("invisible");
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
|
@ -1310,7 +1312,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
searchTabInitialized = true;
|
||||
}
|
||||
|
||||
$("searchTabColumn").classList.remove("invisible");
|
||||
document.getElementById("searchTabColumn").classList.remove("invisible");
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideRssTab();
|
||||
|
@ -1321,7 +1323,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
})();
|
||||
|
||||
const hideSearchTab = () => {
|
||||
$("searchTabColumn").classList.add("invisible");
|
||||
document.getElementById("searchTabColumn").classList.add("invisible");
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
||||
|
@ -1347,7 +1349,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
window.qBittorrent.Rss.load();
|
||||
}
|
||||
|
||||
$("rssTabColumn").classList.remove("invisible");
|
||||
document.getElementById("rssTabColumn").classList.remove("invisible");
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideSearchTab();
|
||||
|
@ -1358,7 +1360,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
})();
|
||||
|
||||
const hideRssTab = () => {
|
||||
$("rssTabColumn").classList.add("invisible");
|
||||
document.getElementById("rssTabColumn").classList.add("invisible");
|
||||
window.qBittorrent.Rss && window.qBittorrent.Rss.unload();
|
||||
MochaUI.Desktop.resizePanels();
|
||||
};
|
||||
|
@ -1385,7 +1387,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
window.qBittorrent.Log.load();
|
||||
}
|
||||
|
||||
$("logTabColumn").classList.remove("invisible");
|
||||
document.getElementById("logTabColumn").classList.remove("invisible");
|
||||
customSyncMainDataInterval = 30000;
|
||||
hideTransfersTab();
|
||||
hideSearchTab();
|
||||
|
@ -1396,7 +1398,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
})();
|
||||
|
||||
const hideLogTab = () => {
|
||||
$("logTabColumn").classList.add("invisible");
|
||||
document.getElementById("logTabColumn").classList.add("invisible");
|
||||
MochaUI.Desktop.resizePanels();
|
||||
window.qBittorrent.Log && window.qBittorrent.Log.unload();
|
||||
};
|
||||
|
@ -1472,11 +1474,11 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
tabsOnload: () => {
|
||||
MochaUI.initializeTabs("panelTabs");
|
||||
|
||||
$("logMessageLink").addEventListener("click", (e) => {
|
||||
document.getElementById("logMessageLink").addEventListener("click", (e) => {
|
||||
window.qBittorrent.Log.setCurrentTab("main");
|
||||
});
|
||||
|
||||
$("logPeerLink").addEventListener("click", (e) => {
|
||||
document.getElementById("logPeerLink").addEventListener("click", (e) => {
|
||||
window.qBittorrent.Log.setCurrentTab("peer");
|
||||
});
|
||||
},
|
||||
|
@ -1618,7 +1620,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
// listen for changes to torrentsFilterInput
|
||||
let torrentsFilterInputTimer = -1;
|
||||
$("torrentsFilterInput").addEventListener("input", () => {
|
||||
document.getElementById("torrentsFilterInput").addEventListener("input", (event) => {
|
||||
clearTimeout(torrentsFilterInputTimer);
|
||||
torrentsFilterInputTimer = setTimeout(() => {
|
||||
torrentsFilterInputTimer = -1;
|
||||
|
@ -1628,24 +1630,24 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
document.getElementById("torrentsFilterToolbar").addEventListener("change", (e) => { torrentsTable.updateTable(); });
|
||||
|
||||
$("transfersTabLink").addEventListener("click", () => { showTransfersTab(); });
|
||||
$("searchTabLink").addEventListener("click", () => { showSearchTab(); });
|
||||
$("rssTabLink").addEventListener("click", () => { showRssTab(); });
|
||||
$("logTabLink").addEventListener("click", () => { showLogTab(); });
|
||||
document.getElementById("transfersTabLink").addEventListener("click", (event) => { showTransfersTab(); });
|
||||
document.getElementById("searchTabLink").addEventListener("click", (event) => { showSearchTab(); });
|
||||
document.getElementById("rssTabLink").addEventListener("click", (event) => { showRssTab(); });
|
||||
document.getElementById("logTabLink").addEventListener("click", (event) => { showLogTab(); });
|
||||
updateTabDisplay();
|
||||
|
||||
const registerDragAndDrop = () => {
|
||||
$("desktop").addEventListener("dragover", (ev) => {
|
||||
document.getElementById("desktop").addEventListener("dragover", (ev) => {
|
||||
if (ev.preventDefault)
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
$("desktop").addEventListener("dragenter", (ev) => {
|
||||
document.getElementById("desktop").addEventListener("dragenter", (ev) => {
|
||||
if (ev.preventDefault)
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
$("desktop").addEventListener("drop", (ev) => {
|
||||
document.getElementById("desktop").addEventListener("drop", (ev) => {
|
||||
if (ev.preventDefault)
|
||||
ev.preventDefault();
|
||||
|
||||
|
@ -1679,7 +1681,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
saveWindowSize(id);
|
||||
}),
|
||||
onContentLoaded: () => {
|
||||
const fileInput = $(`${id}_iframe`).contentDocument.getElementById("fileselect");
|
||||
const fileInput = document.getElementById(`${id}_iframe`).contentDocument.getElementById("fileselect");
|
||||
fileInput.files = droppedFiles;
|
||||
}
|
||||
});
|
||||
|
@ -1793,7 +1795,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
window.addEventListener("load", async (event) => {
|
||||
await window.qBittorrent.Client.initializeCaches();
|
||||
|
||||
// switch to previously used tab
|
||||
|
@ -1801,22 +1803,22 @@ window.addEventListener("load", async () => {
|
|||
switch (previouslyUsedTab) {
|
||||
case "search":
|
||||
if (window.qBittorrent.Client.isShowSearchEngine())
|
||||
$("searchTabLink").click();
|
||||
document.getElementById("searchTabLink").click();
|
||||
break;
|
||||
case "rss":
|
||||
if (window.qBittorrent.Client.isShowRssReader())
|
||||
$("rssTabLink").click();
|
||||
document.getElementById("rssTabLink").click();
|
||||
break;
|
||||
case "log":
|
||||
if (window.qBittorrent.Client.isShowLogViewer())
|
||||
$("logTabLink").click();
|
||||
document.getElementById("logTabLink").click();
|
||||
break;
|
||||
case "transfers":
|
||||
$("transfersTabLink").click();
|
||||
document.getElementById("transfersTabLink").click();
|
||||
break;
|
||||
default:
|
||||
console.error(`Unexpected 'selected_window_tab' value: ${previouslyUsedTab}`);
|
||||
$("transfersTabLink").click();
|
||||
document.getElementById("transfersTabLink").click();
|
||||
break;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -66,7 +66,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
};
|
||||
|
||||
// option diffs menu
|
||||
this.menu = $(this.options.menu);
|
||||
this.menu = document.getElementById(this.options.menu);
|
||||
|
||||
// fx
|
||||
this.fx = new Fx.Tween(this.menu, {
|
||||
|
@ -190,7 +190,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
e.stopPropagation();
|
||||
}
|
||||
// record this as the trigger
|
||||
this.options.element = $(el);
|
||||
this.options.element = el;
|
||||
this.adjustMenuPosition(e);
|
||||
// show the menu
|
||||
this.show();
|
||||
|
@ -219,7 +219,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
});
|
||||
|
||||
// hide on body click
|
||||
$(document.body).addEventListener("click", () => {
|
||||
document.body.addEventListener("click", (event) => {
|
||||
this.hide();
|
||||
this.options.element = null;
|
||||
});
|
||||
|
@ -292,7 +292,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
this.options.actions[action](element, this, action);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class FilterListContextMenu extends ContextMenu {
|
||||
constructor(options) {
|
||||
|
@ -316,7 +316,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
.setEnabled("stopTorrents", torrentsVisible)
|
||||
.setEnabled("deleteTorrents", torrentsVisible);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class TorrentsTableContextMenu extends ContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -457,7 +457,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
this.setEnabled("copyInfohash1", thereAreV1Hashes);
|
||||
this.setEnabled("copyInfohash2", thereAreV2Hashes);
|
||||
|
||||
const contextTagList = $("contextTagList");
|
||||
const contextTagList = document.getElementById("contextTagList");
|
||||
for (const tag of tagMap.keys()) {
|
||||
const checkbox = contextTagList.querySelector(`a[href="#Tag/${tag}"] input[type="checkbox"]`);
|
||||
const count = tagCount.get(tag);
|
||||
|
@ -477,13 +477,13 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
}
|
||||
|
||||
updateCategoriesSubMenu(categories) {
|
||||
const contextCategoryList = $("contextCategoryList");
|
||||
[...contextCategoryList.children].forEach((el) => { el.destroy(); });
|
||||
const contextCategoryList = document.getElementById("contextCategoryList");
|
||||
[...contextCategoryList.children].forEach((el) => { el.remove(); });
|
||||
|
||||
const createMenuItem = (text, imgURL, clickFn) => {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.textContent = text;
|
||||
anchor.addEventListener("click", () => { clickFn(); });
|
||||
anchor.addEventListener("click", clickFn);
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = imgURL;
|
||||
|
@ -495,8 +495,8 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
return item;
|
||||
};
|
||||
contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentNewCategoryFN));
|
||||
contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", () => { torrentSetCategoryFN(""); }));
|
||||
contextCategoryList.appendChild(createMenuItem("QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", (event) => { torrentNewCategoryFN(); }));
|
||||
contextCategoryList.appendChild(createMenuItem("QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", (event) => { torrentSetCategoryFN(""); }));
|
||||
|
||||
const sortedCategories = [...categories.keys()];
|
||||
sortedCategories.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
|
||||
|
@ -527,13 +527,13 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
}
|
||||
|
||||
updateTagsSubMenu(tags) {
|
||||
const contextTagList = $("contextTagList");
|
||||
const contextTagList = document.getElementById("contextTagList");
|
||||
contextTagList.replaceChildren();
|
||||
|
||||
const createMenuItem = (text, imgURL, clickFn) => {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.textContent = text;
|
||||
anchor.addEventListener("click", () => { clickFn(); });
|
||||
anchor.addEventListener("click", clickFn);
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = imgURL;
|
||||
|
@ -545,8 +545,8 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
return item;
|
||||
};
|
||||
contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", torrentAddTagsFN));
|
||||
contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", torrentRemoveAllTagsFN));
|
||||
contextTagList.appendChild(createMenuItem("QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]", "images/list-add.svg", (event) => { torrentAddTagsFN(); }));
|
||||
contextTagList.appendChild(createMenuItem("QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]", "images/edit-clear.svg", (event) => { torrentRemoveAllTagsFN(); }));
|
||||
|
||||
const sortedTags = [...tags.keys()];
|
||||
sortedTags.sort(window.qBittorrent.Misc.naturalSortCollator.compare);
|
||||
|
@ -577,13 +577,13 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
contextTagList.appendChild(setTagItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class StatusesFilterContextMenu extends FilterListContextMenu {
|
||||
updateMenuItems() {
|
||||
this.updateTorrentActions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class CategoriesFilterContextMenu extends FilterListContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -604,7 +604,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
this.updateTorrentActions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class TagsFilterContextMenu extends FilterListContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -616,7 +616,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
this.updateTorrentActions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class TrackersFilterContextMenu extends FilterListContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -628,7 +628,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
this.updateTorrentActions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class SearchPluginsTableContextMenu extends ContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -642,7 +642,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
|
||||
this.showItem("Uninstall");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class RssFeedContextMenu extends ContextMenu {
|
||||
updateMenuItems() {
|
||||
|
@ -715,9 +715,9 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class RssArticleContextMenu extends ContextMenu {};
|
||||
class RssArticleContextMenu extends ContextMenu {}
|
||||
|
||||
class RssDownloaderRuleContextMenu extends ContextMenu {
|
||||
adjustMenuPosition(e) {
|
||||
|
@ -727,8 +727,8 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
this.menu.style.left = "-999em";
|
||||
this.menu.style.top = "-999em";
|
||||
// position the menu
|
||||
let xPosMenu = e.pageX + this.options.offsets.x - $("rssdownloaderpage").offsetLeft;
|
||||
let yPosMenu = e.pageY + this.options.offsets.y - $("rssdownloaderpage").offsetTop;
|
||||
let xPosMenu = e.pageX + this.options.offsets.x - document.getElementById("rssdownloaderpage").offsetLeft;
|
||||
let yPosMenu = e.pageY + this.options.offsets.y - document.getElementById("rssdownloaderpage").offsetTop;
|
||||
if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
|
||||
xPosMenu -= this.menu.offsetWidth;
|
||||
if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
|
||||
|
@ -765,7 +765,7 @@ window.qBittorrent.ContextMenu ??= (() => {
|
|||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
|
|
@ -55,7 +55,7 @@ window.qBittorrent.Download ??= (() => {
|
|||
const option = document.createElement("option");
|
||||
option.value = category.name;
|
||||
option.textContent = category.name;
|
||||
$("categorySelect").appendChild(option);
|
||||
document.getElementById("categorySelect").appendChild(option);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -64,31 +64,31 @@ window.qBittorrent.Download ??= (() => {
|
|||
const pref = window.parent.qBittorrent.Cache.preferences.get();
|
||||
|
||||
defaultSavePath = pref.save_path;
|
||||
$("savepath").value = defaultSavePath;
|
||||
$("startTorrent").checked = !pref.add_stopped_enabled;
|
||||
$("addToTopOfQueue").checked = pref.add_to_top_of_queue;
|
||||
document.getElementById("savepath").value = defaultSavePath;
|
||||
document.getElementById("startTorrent").checked = !pref.add_stopped_enabled;
|
||||
document.getElementById("addToTopOfQueue").checked = pref.add_to_top_of_queue;
|
||||
|
||||
if (pref.auto_tmm_enabled) {
|
||||
$("autoTMM").selectedIndex = 1;
|
||||
$("savepath").disabled = true;
|
||||
document.getElementById("autoTMM").selectedIndex = 1;
|
||||
document.getElementById("savepath").disabled = true;
|
||||
}
|
||||
else {
|
||||
$("autoTMM").selectedIndex = 0;
|
||||
document.getElementById("autoTMM").selectedIndex = 0;
|
||||
}
|
||||
|
||||
if (pref.torrent_stop_condition === "MetadataReceived")
|
||||
$("stopCondition").selectedIndex = 1;
|
||||
document.getElementById("stopCondition").selectedIndex = 1;
|
||||
else if (pref.torrent_stop_condition === "FilesChecked")
|
||||
$("stopCondition").selectedIndex = 2;
|
||||
document.getElementById("stopCondition").selectedIndex = 2;
|
||||
else
|
||||
$("stopCondition").selectedIndex = 0;
|
||||
document.getElementById("stopCondition").selectedIndex = 0;
|
||||
|
||||
if (pref.torrent_content_layout === "Subfolder")
|
||||
$("contentLayout").selectedIndex = 1;
|
||||
document.getElementById("contentLayout").selectedIndex = 1;
|
||||
else if (pref.torrent_content_layout === "NoSubfolder")
|
||||
$("contentLayout").selectedIndex = 2;
|
||||
document.getElementById("contentLayout").selectedIndex = 2;
|
||||
else
|
||||
$("contentLayout").selectedIndex = 0;
|
||||
document.getElementById("contentLayout").selectedIndex = 0;
|
||||
};
|
||||
|
||||
const changeCategorySelect = (item) => {
|
||||
|
@ -97,41 +97,41 @@ window.qBittorrent.Download ??= (() => {
|
|||
item.nextElementSibling.value = "";
|
||||
item.nextElementSibling.select();
|
||||
|
||||
if ($("autoTMM").selectedIndex === 1)
|
||||
$("savepath").value = defaultSavePath;
|
||||
if (document.getElementById("autoTMM").selectedIndex === 1)
|
||||
document.getElementById("savepath").value = defaultSavePath;
|
||||
}
|
||||
else {
|
||||
item.nextElementSibling.hidden = true;
|
||||
const text = item.options[item.selectedIndex].textContent;
|
||||
item.nextElementSibling.value = text;
|
||||
|
||||
if ($("autoTMM").selectedIndex === 1) {
|
||||
if (document.getElementById("autoTMM").selectedIndex === 1) {
|
||||
const categoryName = item.value;
|
||||
const category = categories[categoryName];
|
||||
let savePath = defaultSavePath;
|
||||
if (category !== undefined)
|
||||
savePath = (category["savePath"] !== "") ? category["savePath"] : `${defaultSavePath}/${categoryName}`;
|
||||
$("savepath").value = savePath;
|
||||
document.getElementById("savepath").value = savePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeTMM = (item) => {
|
||||
if (item.selectedIndex === 1) {
|
||||
$("savepath").disabled = true;
|
||||
document.getElementById("savepath").disabled = true;
|
||||
|
||||
const categorySelect = $("categorySelect");
|
||||
const categorySelect = document.getElementById("categorySelect");
|
||||
const categoryName = categorySelect.options[categorySelect.selectedIndex].value;
|
||||
const category = categories[categoryName];
|
||||
$("savepath").value = (category === undefined) ? "" : category["savePath"];
|
||||
document.getElementById("savepath").value = (category === undefined) ? "" : category["savePath"];
|
||||
}
|
||||
else {
|
||||
$("savepath").disabled = false;
|
||||
$("savepath").value = defaultSavePath;
|
||||
document.getElementById("savepath").disabled = false;
|
||||
document.getElementById("savepath").value = defaultSavePath;
|
||||
}
|
||||
};
|
||||
|
||||
$(window).addEventListener("load", async () => {
|
||||
window.addEventListener("load", async (event) => {
|
||||
// user might load this page directly (via browser magnet handler)
|
||||
// so wait for crucial initialization to complete
|
||||
await window.parent.qBittorrent.Client.initializeCaches();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -56,51 +56,51 @@ window.qBittorrent.FileTree ??= (() => {
|
|||
};
|
||||
Object.freeze(TriState);
|
||||
|
||||
const FileTree = new Class({
|
||||
root: null,
|
||||
nodeMap: {},
|
||||
class FileTree {
|
||||
#root = null;
|
||||
#nodeMap = {}; // Object with Number as keys is faster than anything
|
||||
|
||||
setRoot: function(root) {
|
||||
this.root = root;
|
||||
this.generateNodeMap(root);
|
||||
setRoot(root) {
|
||||
this.#root = root;
|
||||
this.#generateNodeMap(root);
|
||||
|
||||
if (this.root.isFolder)
|
||||
this.root.calculateSize();
|
||||
},
|
||||
if (this.#root.isFolder)
|
||||
this.#root.calculateSize();
|
||||
}
|
||||
|
||||
getRoot: function() {
|
||||
return this.root;
|
||||
},
|
||||
getRoot() {
|
||||
return this.#root;
|
||||
}
|
||||
|
||||
generateNodeMap: function(root) {
|
||||
#generateNodeMap(root) {
|
||||
const stack = [root];
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop();
|
||||
|
||||
// don't store root node in map
|
||||
if (node.root !== null)
|
||||
this.nodeMap[node.rowId] = node;
|
||||
this.#nodeMap[node.rowId] = node;
|
||||
|
||||
stack.push(...node.children);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getNode: function(rowId) {
|
||||
return (this.nodeMap[rowId] === undefined)
|
||||
? null
|
||||
: this.nodeMap[rowId];
|
||||
},
|
||||
getNode(rowId) {
|
||||
// TODO: enforce caller sites to pass `rowId` as number and not string
|
||||
const value = this.#nodeMap[Number(rowId)];
|
||||
return (value !== undefined) ? value : null;
|
||||
}
|
||||
|
||||
getRowId: (node) => {
|
||||
getRowId(node) {
|
||||
return node.rowId;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nodes in DFS in-order
|
||||
*/
|
||||
toArray: function() {
|
||||
toArray() {
|
||||
const ret = [];
|
||||
const stack = this.root.children.toReversed();
|
||||
const stack = this.#root.children.toReversed();
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop();
|
||||
ret.push(node);
|
||||
|
@ -108,45 +108,40 @@ window.qBittorrent.FileTree ??= (() => {
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const FileNode = new Class({
|
||||
name: "",
|
||||
path: "",
|
||||
rowId: null,
|
||||
size: 0,
|
||||
checked: TriState.Unchecked,
|
||||
remaining: 0,
|
||||
progress: 0,
|
||||
priority: FilePriority.Normal,
|
||||
availability: 0,
|
||||
depth: 0,
|
||||
root: null,
|
||||
data: null,
|
||||
isFolder: false,
|
||||
children: [],
|
||||
});
|
||||
|
||||
const FolderNode = new Class({
|
||||
Extends: FileNode,
|
||||
class FileNode {
|
||||
name = "";
|
||||
path = "";
|
||||
rowId = null;
|
||||
size = 0;
|
||||
checked = TriState.Unchecked;
|
||||
remaining = 0;
|
||||
progress = 0;
|
||||
priority = FilePriority.Normal;
|
||||
availability = 0;
|
||||
depth = 0;
|
||||
root = null;
|
||||
data = null;
|
||||
isFolder = false;
|
||||
children = [];
|
||||
}
|
||||
|
||||
class FolderNode extends FileNode {
|
||||
/**
|
||||
* Will automatically tick the checkbox for a folder if all subfolders and files are also ticked
|
||||
*/
|
||||
autoCheckFolders: true,
|
||||
autoCheckFolders = true;
|
||||
isFolder = true;
|
||||
|
||||
initialize: function() {
|
||||
this.isFolder = true;
|
||||
},
|
||||
|
||||
addChild: function(node) {
|
||||
addChild(node) {
|
||||
this.children.push(node);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size of node and its children
|
||||
*/
|
||||
calculateSize: function() {
|
||||
calculateSize() {
|
||||
const stack = [this];
|
||||
const visited = [];
|
||||
|
||||
|
@ -202,7 +197,7 @@ window.qBittorrent.FileTree ??= (() => {
|
|||
stack.pop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
|
|
@ -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);
|
||||
|
@ -61,6 +67,40 @@ window.qBittorrent.LocalPreferences ??= (() => {
|
|||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -151,7 +151,7 @@ let exportTorrentFN = () => {};
|
|||
|
||||
const initializeWindows = () => {
|
||||
saveWindowSize = (windowId) => {
|
||||
const size = $(windowId).getSize();
|
||||
const size = document.getElementById(windowId).getSize();
|
||||
LocalPreferences.set(`window_${windowId}_width`, size.x);
|
||||
LocalPreferences.set(`window_${windowId}_height`, size.y);
|
||||
};
|
||||
|
@ -166,8 +166,8 @@ const initializeWindows = () => {
|
|||
|
||||
const addClickEvent = (el, fn) => {
|
||||
["Link", "Button"].each((item) => {
|
||||
if ($(el + item))
|
||||
$(el + item).addEventListener("click", fn);
|
||||
if (document.getElementById(el + item))
|
||||
document.getElementById(el + item).addEventListener("click", fn);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
72
src/webui/www/private/scripts/monkeypatch.js
Normal file
72
src/webui/www/private/scripts/monkeypatch.js
Normal 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();
|
|
@ -76,16 +76,16 @@ window.qBittorrent.pathAutofill ??= (() => {
|
|||
function attachPathAutofill() {
|
||||
const directoryInputs = document.querySelectorAll(".pathDirectory:not(.pathAutoFillInitialized)");
|
||||
for (const input of directoryInputs) {
|
||||
input.addEventListener("input", function() { showPathSuggestions(this, "dirs"); });
|
||||
input.addEventListener("input", function(event) { showPathSuggestions(this, "dirs"); });
|
||||
input.classList.add("pathAutoFillInitialized");
|
||||
}
|
||||
|
||||
const fileInputs = document.querySelectorAll(".pathFile:not(.pathAutoFillInitialized)");
|
||||
for (const input of fileInputs) {
|
||||
input.addEventListener("input", function() { showPathSuggestions(this, "all"); });
|
||||
input.addEventListener("input", function(event) { showPathSuggestions(this, "all"); });
|
||||
input.classList.add("pathAutoFillInitialized");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
|
|
@ -36,229 +36,211 @@ window.qBittorrent.PiecesBar ??= (() => {
|
|||
};
|
||||
};
|
||||
|
||||
const STATUS_DOWNLOADING = 1;
|
||||
const STATUS_DOWNLOADED = 2;
|
||||
class PiecesBar extends HTMLElement {
|
||||
static #STATUS_DOWNLOADING = 1;
|
||||
static #STATUS_DOWNLOADED = 2;
|
||||
// absolute max width of 4096
|
||||
// this is to support all browsers for size of canvas elements
|
||||
// see https://github.com/jhildenbiddle/canvas-size#test-results
|
||||
static #MAX_CANVAS_WIDTH = 4096;
|
||||
static #piecesBarUniqueId = 0;
|
||||
|
||||
// absolute max width of 4096
|
||||
// this is to support all browsers for size of canvas elements
|
||||
// see https://github.com/jhildenbiddle/canvas-size#test-results
|
||||
const MAX_CANVAS_WIDTH = 4096;
|
||||
#canvasEl;
|
||||
#ctx;
|
||||
#pieces;
|
||||
#styles;
|
||||
#id = ++PiecesBar.#piecesBarUniqueId;
|
||||
#resizeObserver;
|
||||
|
||||
let piecesBarUniqueId = 0;
|
||||
const PiecesBar = new Class({
|
||||
initialize: (pieces, parameters) => {
|
||||
const vals = {
|
||||
id: `piecesbar_${piecesBarUniqueId++}`,
|
||||
width: 0,
|
||||
height: 0,
|
||||
constructor(pieces, styles = {}) {
|
||||
super();
|
||||
this.setPieces(pieces);
|
||||
this.#styles = {
|
||||
height: 12,
|
||||
downloadingColor: "hsl(110deg 94% 27%)", // @TODO palette vars not supported for this value, apply average
|
||||
haveColor: "hsl(210deg 55% 55%)", // @TODO palette vars not supported for this value, apply average
|
||||
borderSize: 1,
|
||||
borderColor: "var(--color-border-default)"
|
||||
borderColor: "var(--color-border-default)",
|
||||
...styles
|
||||
};
|
||||
|
||||
if (parameters && (typeOf(parameters) === "object"))
|
||||
Object.append(vals, parameters);
|
||||
vals.height = Math.max(vals.height, 12);
|
||||
this.#canvasEl = document.createElement("canvas");
|
||||
this.#canvasEl.style.height = "100%";
|
||||
this.#canvasEl.style.imageRendering = "pixelated";
|
||||
this.#canvasEl.style.width = "100%";
|
||||
this.#ctx = this.#canvasEl.getContext("2d");
|
||||
|
||||
const obj = document.createElement("div");
|
||||
obj.id = vals.id;
|
||||
obj.className = "piecesbarWrapper";
|
||||
obj.style.border = `${vals.borderSize}px solid ${vals.borderColor}`;
|
||||
obj.style.height = `${vals.height}px`;
|
||||
obj.vals = vals;
|
||||
obj.vals.pieces = [pieces, []].pick();
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.shadowRoot.host.id = `piecesbar_${this.#id}`;
|
||||
this.shadowRoot.host.style.display = "block";
|
||||
this.shadowRoot.host.style.height = `${this.#styles.height}px`;
|
||||
this.shadowRoot.host.style.border = `${this.#styles.borderSize}px solid ${this.#styles.borderColor}`;
|
||||
this.shadowRoot.append(this.#canvasEl);
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.id = `${vals.id}_canvas`;
|
||||
canvas.className = "piecesbarCanvas";
|
||||
canvas.width = `${vals.width - (2 * vals.borderSize)}`;
|
||||
canvas.height = "1"; // will stretch vertically to take up the height of the parent
|
||||
obj.vals.canvas = canvas;
|
||||
obj.appendChild(obj.vals.canvas);
|
||||
|
||||
obj.setPieces = setPieces;
|
||||
obj.refresh = refresh;
|
||||
obj.clear = setPieces.bind(obj, []);
|
||||
obj._drawStatus = drawStatus;
|
||||
|
||||
if (vals.width > 0)
|
||||
obj.setPieces(vals.pieces);
|
||||
else
|
||||
setTimeout(() => { checkForParent(obj.id); });
|
||||
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
|
||||
function setPieces(pieces) {
|
||||
if (!Array.isArray(pieces))
|
||||
pieces = [];
|
||||
|
||||
this.vals.pieces = pieces;
|
||||
this.refresh(true);
|
||||
}
|
||||
|
||||
function refresh(force) {
|
||||
if (!this.parentNode)
|
||||
return;
|
||||
|
||||
const pieces = this.vals.pieces;
|
||||
|
||||
// if the number of pieces is small, use that for the width,
|
||||
// and have it stretch horizontally.
|
||||
// this also limits the ratio below to >= 1
|
||||
const width = Math.min(this.offsetWidth, pieces.length, MAX_CANVAS_WIDTH);
|
||||
if ((this.vals.width === width) && !force)
|
||||
return;
|
||||
|
||||
this.vals.width = width;
|
||||
|
||||
// change canvas size to fit exactly in the space
|
||||
this.vals.canvas.width = width - (2 * this.vals.borderSize);
|
||||
|
||||
const canvas = this.vals.canvas;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const imageWidth = canvas.width;
|
||||
|
||||
if (imageWidth.length === 0)
|
||||
return;
|
||||
|
||||
let minStatus = Infinity;
|
||||
let maxStatus = 0;
|
||||
|
||||
for (const status of pieces) {
|
||||
if (status > maxStatus)
|
||||
maxStatus = status;
|
||||
if (status < minStatus)
|
||||
minStatus = status;
|
||||
this.#resizeObserver = new ResizeObserver(window.qBittorrent.Misc.createDebounceHandler(100, () => {
|
||||
this.#refresh();
|
||||
}));
|
||||
}
|
||||
|
||||
// if no progress then don't do anything
|
||||
if (maxStatus === 0)
|
||||
return;
|
||||
|
||||
// if all pieces are downloaded, fill entire image at once
|
||||
if (minStatus === STATUS_DOWNLOADED) {
|
||||
ctx.fillStyle = this.vals.haveColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
return;
|
||||
connectedCallback() {
|
||||
this.#resizeObserver.observe(this);
|
||||
this.#refresh();
|
||||
}
|
||||
|
||||
/* Linear transformation from pieces to pixels.
|
||||
*
|
||||
* The canvas size can vary in width so this figures out what to draw at each pixel.
|
||||
* Inspired by the GUI code here https://github.com/qbittorrent/qBittorrent/blob/25b3f2d1a6b14f0fe098fb79a3d034607e52deae/src/gui/properties/downloadedpiecesbar.cpp#L54
|
||||
*
|
||||
* example ratio > 1 (at least 2 pieces per pixel)
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
* pieces | 2 | 1 | 2 | 0 | 2 | 0 | 1 | 0 | 1 | 2 |
|
||||
* +---------+---------+---------+---------+---------+---------+
|
||||
* pixels | | | | | | |
|
||||
* +---------+---------+---------+---------+---------+---------+
|
||||
*
|
||||
* example ratio < 1 (at most 2 pieces per pixel)
|
||||
* This case shouldn't happen since the max pixels are limited to the number of pieces
|
||||
* +---------+---------+---------+---------+----------+--------+
|
||||
* pieces | 2 | 1 | 1 | 0 | 2 | 2 |
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
* pixels | | | | | | | | | | |
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
*/
|
||||
clear() {
|
||||
this.setPieces([]);
|
||||
}
|
||||
|
||||
const ratio = pieces.length / imageWidth;
|
||||
setPieces(pieces) {
|
||||
this.#pieces = !Array.isArray(pieces) ? [] : pieces;
|
||||
this.#refresh();
|
||||
}
|
||||
|
||||
let lastValue = null;
|
||||
let rectangleStart = 0;
|
||||
#refresh() {
|
||||
if (!this.isConnected)
|
||||
return;
|
||||
|
||||
// for each pixel compute its status based on the pieces
|
||||
for (let x = 0; x < imageWidth; ++x) {
|
||||
// find positions in the pieces array
|
||||
const piecesFrom = x * ratio;
|
||||
const piecesTo = (x + 1) * ratio;
|
||||
const piecesToInt = Math.ceil(piecesTo);
|
||||
// if the number of pieces is small, use that for the width,
|
||||
// and have it stretch horizontally.
|
||||
// this also limits the ratio below to >= 1
|
||||
const width = Math.min(this.offsetWidth, this.#pieces.length, PiecesBar.#MAX_CANVAS_WIDTH);
|
||||
|
||||
const statusValues = {
|
||||
[STATUS_DOWNLOADING]: 0,
|
||||
[STATUS_DOWNLOADED]: 0
|
||||
};
|
||||
// change canvas size to fit exactly in the space
|
||||
this.#canvasEl.width = width - (2 * this.#styles.borderSize);
|
||||
|
||||
// aggregate the status of each piece that contributes to this pixel
|
||||
for (let p = piecesFrom; p < piecesToInt; ++p) {
|
||||
const piece = Math.floor(p);
|
||||
const pieceStart = Math.max(piecesFrom, piece);
|
||||
const pieceEnd = Math.min(piece + 1, piecesTo);
|
||||
this.#ctx.clearRect(0, 0, this.#canvasEl.width, this.#canvasEl.height);
|
||||
|
||||
const amount = pieceEnd - pieceStart;
|
||||
const status = pieces[piece];
|
||||
const imageWidth = this.#canvasEl.width;
|
||||
|
||||
if (status in statusValues)
|
||||
statusValues[status] += amount;
|
||||
if (imageWidth.length === 0)
|
||||
return;
|
||||
|
||||
let minStatus = Infinity;
|
||||
let maxStatus = 0;
|
||||
|
||||
for (const status of this.#pieces) {
|
||||
if (status > maxStatus)
|
||||
maxStatus = status;
|
||||
if (status < minStatus)
|
||||
minStatus = status;
|
||||
}
|
||||
|
||||
// normalize to interval [0, 1]
|
||||
statusValues[STATUS_DOWNLOADING] /= ratio;
|
||||
statusValues[STATUS_DOWNLOADED] /= ratio;
|
||||
// if no progress then don't do anything
|
||||
if (maxStatus === 0)
|
||||
return;
|
||||
|
||||
// floats accumulate small errors, so smooth it out by rounding to hundredths place
|
||||
// this effectively limits each status to a value 1 in 100
|
||||
statusValues[STATUS_DOWNLOADING] = Math.round(statusValues[STATUS_DOWNLOADING] * 100) / 100;
|
||||
statusValues[STATUS_DOWNLOADED] = Math.round(statusValues[STATUS_DOWNLOADED] * 100) / 100;
|
||||
// if all pieces are downloaded, fill entire image at once
|
||||
if (minStatus === PiecesBar.#STATUS_DOWNLOADED) {
|
||||
this.#ctx.fillStyle = this.#styles.haveColor;
|
||||
this.#ctx.fillRect(0, 0, this.#canvasEl.width, this.#canvasEl.height);
|
||||
return;
|
||||
}
|
||||
|
||||
// float precision sometimes _still_ gives > 1
|
||||
statusValues[STATUS_DOWNLOADING] = Math.min(statusValues[STATUS_DOWNLOADING], 1);
|
||||
statusValues[STATUS_DOWNLOADED] = Math.min(statusValues[STATUS_DOWNLOADED], 1);
|
||||
/* Linear transformation from pieces to pixels.
|
||||
*
|
||||
* The canvas size can vary in width so this figures out what to draw at each pixel.
|
||||
* Inspired by the GUI code here https://github.com/qbittorrent/qBittorrent/blob/25b3f2d1a6b14f0fe098fb79a3d034607e52deae/src/gui/properties/downloadedpiecesbar.cpp#L54
|
||||
*
|
||||
* example ratio > 1 (at least 2 pieces per pixel)
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
* pieces | 2 | 1 | 2 | 0 | 2 | 0 | 1 | 0 | 1 | 2 |
|
||||
* +---------+---------+---------+---------+---------+---------+
|
||||
* pixels | | | | | | |
|
||||
* +---------+---------+---------+---------+---------+---------+
|
||||
*
|
||||
* example ratio < 1 (at most 2 pieces per pixel)
|
||||
* This case shouldn't happen since the max pixels are limited to the number of pieces
|
||||
* +---------+---------+---------+---------+----------+--------+
|
||||
* pieces | 2 | 1 | 1 | 0 | 2 | 2 |
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
* pixels | | | | | | | | | | |
|
||||
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
*/
|
||||
|
||||
const ratio = this.#pieces.length / imageWidth;
|
||||
|
||||
let lastValue = null;
|
||||
let rectangleStart = 0;
|
||||
|
||||
// for each pixel compute its status based on the pieces
|
||||
for (let x = 0; x < imageWidth; ++x) {
|
||||
// find positions in the pieces array
|
||||
const piecesFrom = x * ratio;
|
||||
const piecesTo = (x + 1) * ratio;
|
||||
const piecesToInt = Math.ceil(piecesTo);
|
||||
|
||||
const statusValues = {
|
||||
[PiecesBar.#STATUS_DOWNLOADING]: 0,
|
||||
[PiecesBar.#STATUS_DOWNLOADED]: 0
|
||||
};
|
||||
|
||||
// aggregate the status of each piece that contributes to this pixel
|
||||
for (let p = piecesFrom; p < piecesToInt; ++p) {
|
||||
const piece = Math.floor(p);
|
||||
const pieceStart = Math.max(piecesFrom, piece);
|
||||
const pieceEnd = Math.min(piece + 1, piecesTo);
|
||||
|
||||
const amount = pieceEnd - pieceStart;
|
||||
const status = this.#pieces[piece];
|
||||
|
||||
if (status in statusValues)
|
||||
statusValues[status] += amount;
|
||||
}
|
||||
|
||||
// normalize to interval [0, 1]
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADING] /= ratio;
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADED] /= ratio;
|
||||
|
||||
// floats accumulate small errors, so smooth it out by rounding to hundredths place
|
||||
// this effectively limits each status to a value 1 in 100
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADING] = Math.round(statusValues[PiecesBar.#STATUS_DOWNLOADING] * 100) / 100;
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADED] = Math.round(statusValues[PiecesBar.#STATUS_DOWNLOADED] * 100) / 100;
|
||||
|
||||
// float precision sometimes _still_ gives > 1
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADING] = Math.min(statusValues[PiecesBar.#STATUS_DOWNLOADING], 1);
|
||||
statusValues[PiecesBar.#STATUS_DOWNLOADED] = Math.min(statusValues[PiecesBar.#STATUS_DOWNLOADED], 1);
|
||||
|
||||
if (!lastValue)
|
||||
lastValue = statusValues;
|
||||
|
||||
// group contiguous colors together and draw as a single rectangle
|
||||
if ((lastValue[PiecesBar.#STATUS_DOWNLOADING] === statusValues[PiecesBar.#STATUS_DOWNLOADING])
|
||||
&& (lastValue[PiecesBar.#STATUS_DOWNLOADED] === statusValues[PiecesBar.#STATUS_DOWNLOADED]))
|
||||
continue;
|
||||
|
||||
const rectangleWidth = x - rectangleStart;
|
||||
this.#drawStatus(rectangleStart, rectangleWidth, lastValue);
|
||||
|
||||
if (!lastValue)
|
||||
lastValue = statusValues;
|
||||
rectangleStart = x;
|
||||
}
|
||||
|
||||
// group contiguous colors together and draw as a single rectangle
|
||||
if ((lastValue[STATUS_DOWNLOADING] === statusValues[STATUS_DOWNLOADING])
|
||||
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED]))
|
||||
continue;
|
||||
|
||||
const rectangleWidth = x - rectangleStart;
|
||||
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||
|
||||
lastValue = statusValues;
|
||||
rectangleStart = x;
|
||||
// fill a rect at the end of the canvas
|
||||
if (rectangleStart < imageWidth) {
|
||||
const rectangleWidth = imageWidth - rectangleStart;
|
||||
this.#drawStatus(rectangleStart, rectangleWidth, lastValue);
|
||||
}
|
||||
}
|
||||
|
||||
// fill a rect at the end of the canvas
|
||||
if (rectangleStart < imageWidth) {
|
||||
const rectangleWidth = imageWidth - rectangleStart;
|
||||
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||
#drawStatus(start, width, statusValues) {
|
||||
// mix the colors by using transparency and a composite mode
|
||||
this.#ctx.globalCompositeOperation = "lighten";
|
||||
|
||||
if (statusValues[PiecesBar.#STATUS_DOWNLOADING]) {
|
||||
this.#ctx.globalAlpha = statusValues[PiecesBar.#STATUS_DOWNLOADING];
|
||||
this.#ctx.fillStyle = this.#styles.downloadingColor;
|
||||
this.#ctx.fillRect(start, 0, width, this.#canvasEl.height);
|
||||
}
|
||||
|
||||
if (statusValues[PiecesBar.#STATUS_DOWNLOADED]) {
|
||||
this.#ctx.globalAlpha = statusValues[PiecesBar.#STATUS_DOWNLOADED];
|
||||
this.#ctx.fillStyle = this.#styles.haveColor;
|
||||
this.#ctx.fillRect(start, 0, width, this.#canvasEl.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawStatus(ctx, start, width, statusValues) {
|
||||
// mix the colors by using transparency and a composite mode
|
||||
ctx.globalCompositeOperation = "lighten";
|
||||
|
||||
if (statusValues[STATUS_DOWNLOADING]) {
|
||||
ctx.globalAlpha = statusValues[STATUS_DOWNLOADING];
|
||||
ctx.fillStyle = this.vals.downloadingColor;
|
||||
ctx.fillRect(start, 0, width, ctx.canvas.height);
|
||||
}
|
||||
|
||||
if (statusValues[STATUS_DOWNLOADED]) {
|
||||
ctx.globalAlpha = statusValues[STATUS_DOWNLOADED];
|
||||
ctx.fillStyle = this.vals.haveColor;
|
||||
ctx.fillRect(start, 0, width, ctx.canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
const checkForParent = (id) => {
|
||||
const obj = $(id);
|
||||
if (!obj)
|
||||
return;
|
||||
if (!obj.parentNode)
|
||||
return setTimeout(() => { checkForParent(id); }, 100);
|
||||
|
||||
obj.refresh();
|
||||
};
|
||||
customElements.define("pieces-bar", PiecesBar);
|
||||
|
||||
return exports();
|
||||
})();
|
||||
|
|
|
@ -142,7 +142,7 @@ window.qBittorrent.ProgressBar ??= (() => {
|
|||
}
|
||||
|
||||
const ProgressBar_checkForParent = (id) => {
|
||||
const obj = $(id);
|
||||
const obj = document.getElementById(id);
|
||||
if (!obj)
|
||||
return;
|
||||
if (!obj.parentNode)
|
||||
|
|
|
@ -208,7 +208,7 @@ window.qBittorrent.PropFiles ??= (() => {
|
|||
const rowIds = [];
|
||||
const fileIds = [];
|
||||
let priority = FilePriority.Ignored;
|
||||
const checkbox = $("tristate_cb");
|
||||
const checkbox = document.getElementById("tristate_cb");
|
||||
|
||||
if (checkbox.state === "checked") {
|
||||
setCheckboxUnchecked(checkbox);
|
||||
|
@ -245,7 +245,7 @@ window.qBittorrent.PropFiles ??= (() => {
|
|||
};
|
||||
|
||||
const updateGlobalCheckbox = () => {
|
||||
const checkbox = $("tristate_cb");
|
||||
const checkbox = document.getElementById("tristate_cb");
|
||||
if (torrentFilesTable.isAllCheckboxesChecked())
|
||||
setCheckboxChecked(checkbox);
|
||||
else if (torrentFilesTable.isAllCheckboxesUnchecked())
|
||||
|
@ -297,7 +297,7 @@ window.qBittorrent.PropFiles ??= (() => {
|
|||
ids.forEach((id) => {
|
||||
torrentFilesTable.setIgnored(id, ignore);
|
||||
|
||||
const combobox = $(`comboPrio${id}`);
|
||||
const combobox = document.getElementById(`comboPrio${id}`);
|
||||
if (combobox !== null)
|
||||
selectComboboxPriority(combobox, priority);
|
||||
});
|
||||
|
@ -307,8 +307,8 @@ window.qBittorrent.PropFiles ??= (() => {
|
|||
const loadTorrentFilesData = () => {
|
||||
if (document.hidden)
|
||||
return;
|
||||
if ($("propFiles").classList.contains("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
if (document.getElementById("propFiles").classList.contains("invisible")
|
||||
|| document.getElementById("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
@ -586,10 +586,10 @@ window.qBittorrent.PropFiles ??= (() => {
|
|||
|
||||
// listen for changes to torrentFilesFilterInput
|
||||
let torrentFilesFilterInputTimer = -1;
|
||||
$("torrentFilesFilterInput").addEventListener("input", () => {
|
||||
document.getElementById("torrentFilesFilterInput").addEventListener("input", (event) => {
|
||||
clearTimeout(torrentFilesFilterInputTimer);
|
||||
|
||||
const value = $("torrentFilesFilterInput").value;
|
||||
const value = document.getElementById("torrentFilesFilterInput").value;
|
||||
torrentFilesTable.setFilter(value);
|
||||
|
||||
torrentFilesFilterInputTimer = setTimeout(() => {
|
||||
|
|
|
@ -40,37 +40,37 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
const piecesBar = new window.qBittorrent.PiecesBar.PiecesBar([], {
|
||||
height: 18
|
||||
});
|
||||
$("progress").appendChild(piecesBar);
|
||||
document.getElementById("progress").appendChild(piecesBar);
|
||||
|
||||
const clearData = () => {
|
||||
document.getElementById("progressPercentage").textContent = "";
|
||||
$("time_elapsed").textContent = "";
|
||||
$("eta").textContent = "";
|
||||
$("nb_connections").textContent = "";
|
||||
$("total_downloaded").textContent = "";
|
||||
$("total_uploaded").textContent = "";
|
||||
$("dl_speed").textContent = "";
|
||||
$("up_speed").textContent = "";
|
||||
$("dl_limit").textContent = "";
|
||||
$("up_limit").textContent = "";
|
||||
$("total_wasted").textContent = "";
|
||||
$("seeds").textContent = "";
|
||||
$("peers").textContent = "";
|
||||
$("share_ratio").textContent = "";
|
||||
$("popularity").textContent = "";
|
||||
$("reannounce").textContent = "";
|
||||
$("last_seen").textContent = "";
|
||||
$("total_size").textContent = "";
|
||||
$("pieces").textContent = "";
|
||||
$("created_by").textContent = "";
|
||||
$("addition_date").textContent = "";
|
||||
$("completion_date").textContent = "";
|
||||
$("creation_date").textContent = "";
|
||||
$("torrent_hash_v1").textContent = "";
|
||||
$("torrent_hash_v2").textContent = "";
|
||||
$("save_path").textContent = "";
|
||||
$("comment").textContent = "";
|
||||
$("private").textContent = "";
|
||||
document.getElementById("time_elapsed").textContent = "";
|
||||
document.getElementById("eta").textContent = "";
|
||||
document.getElementById("nb_connections").textContent = "";
|
||||
document.getElementById("total_downloaded").textContent = "";
|
||||
document.getElementById("total_uploaded").textContent = "";
|
||||
document.getElementById("dl_speed").textContent = "";
|
||||
document.getElementById("up_speed").textContent = "";
|
||||
document.getElementById("dl_limit").textContent = "";
|
||||
document.getElementById("up_limit").textContent = "";
|
||||
document.getElementById("total_wasted").textContent = "";
|
||||
document.getElementById("seeds").textContent = "";
|
||||
document.getElementById("peers").textContent = "";
|
||||
document.getElementById("share_ratio").textContent = "";
|
||||
document.getElementById("popularity").textContent = "";
|
||||
document.getElementById("reannounce").textContent = "";
|
||||
document.getElementById("last_seen").textContent = "";
|
||||
document.getElementById("total_size").textContent = "";
|
||||
document.getElementById("pieces").textContent = "";
|
||||
document.getElementById("created_by").textContent = "";
|
||||
document.getElementById("addition_date").textContent = "";
|
||||
document.getElementById("completion_date").textContent = "";
|
||||
document.getElementById("creation_date").textContent = "";
|
||||
document.getElementById("torrent_hash_v1").textContent = "";
|
||||
document.getElementById("torrent_hash_v2").textContent = "";
|
||||
document.getElementById("save_path").textContent = "";
|
||||
document.getElementById("comment").textContent = "";
|
||||
document.getElementById("private").textContent = "";
|
||||
piecesBar.clear();
|
||||
};
|
||||
|
||||
|
@ -78,8 +78,8 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
const loadTorrentData = () => {
|
||||
if (document.hidden)
|
||||
return;
|
||||
if ($("propGeneral").classList.contains("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
if (document.getElementById("propGeneral").classList.contains("invisible")
|
||||
|| document.getElementById("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
@ -100,13 +100,13 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
$("error_div").textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
document.getElementById("error_div").textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
clearTimeout(loadTorrentDataTimer);
|
||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||
return;
|
||||
}
|
||||
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
|
||||
const data = await response.json();
|
||||
if (data) {
|
||||
|
@ -119,70 +119,70 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
.replace("%1", window.qBittorrent.Misc.friendlyDuration(data.time_elapsed))
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyDuration(data.seeding_time))
|
||||
: window.qBittorrent.Misc.friendlyDuration(data.time_elapsed);
|
||||
$("time_elapsed").textContent = timeElapsed;
|
||||
document.getElementById("time_elapsed").textContent = timeElapsed;
|
||||
|
||||
$("eta").textContent = window.qBittorrent.Misc.friendlyDuration(data.eta, window.qBittorrent.Misc.MAX_ETA);
|
||||
document.getElementById("eta").textContent = window.qBittorrent.Misc.friendlyDuration(data.eta, window.qBittorrent.Misc.MAX_ETA);
|
||||
|
||||
const nbConnections = "QBT_TR(%1 (%2 max))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", data.nb_connections)
|
||||
.replace("%2", ((data.nb_connections_limit < 0) ? "∞" : data.nb_connections_limit));
|
||||
$("nb_connections").textContent = nbConnections;
|
||||
document.getElementById("nb_connections").textContent = nbConnections;
|
||||
|
||||
const totalDownloaded = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded))
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded_session));
|
||||
$("total_downloaded").textContent = totalDownloaded;
|
||||
document.getElementById("total_downloaded").textContent = totalDownloaded;
|
||||
|
||||
const totalUploaded = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded))
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded_session));
|
||||
$("total_uploaded").textContent = totalUploaded;
|
||||
document.getElementById("total_uploaded").textContent = totalUploaded;
|
||||
|
||||
const dlSpeed = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.dl_speed, true))
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.dl_speed_avg, true));
|
||||
$("dl_speed").textContent = dlSpeed;
|
||||
document.getElementById("dl_speed").textContent = dlSpeed;
|
||||
|
||||
const upSpeed = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.up_speed, true))
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.up_speed_avg, true));
|
||||
$("up_speed").textContent = upSpeed;
|
||||
document.getElementById("up_speed").textContent = upSpeed;
|
||||
|
||||
const dlLimit = (data.dl_limit === -1)
|
||||
? "∞"
|
||||
: window.qBittorrent.Misc.friendlyUnit(data.dl_limit, true);
|
||||
$("dl_limit").textContent = dlLimit;
|
||||
document.getElementById("dl_limit").textContent = dlLimit;
|
||||
|
||||
const upLimit = (data.up_limit === -1)
|
||||
? "∞"
|
||||
: window.qBittorrent.Misc.friendlyUnit(data.up_limit, true);
|
||||
$("up_limit").textContent = upLimit;
|
||||
document.getElementById("up_limit").textContent = upLimit;
|
||||
|
||||
$("total_wasted").textContent = window.qBittorrent.Misc.friendlyUnit(data.total_wasted);
|
||||
document.getElementById("total_wasted").textContent = window.qBittorrent.Misc.friendlyUnit(data.total_wasted);
|
||||
|
||||
const seeds = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", data.seeds)
|
||||
.replace("%2", data.seeds_total);
|
||||
$("seeds").textContent = seeds;
|
||||
document.getElementById("seeds").textContent = seeds;
|
||||
|
||||
const peers = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", data.peers)
|
||||
.replace("%2", data.peers_total);
|
||||
$("peers").textContent = peers;
|
||||
document.getElementById("peers").textContent = peers;
|
||||
|
||||
$("share_ratio").textContent = data.share_ratio.toFixed(2);
|
||||
document.getElementById("share_ratio").textContent = data.share_ratio.toFixed(2);
|
||||
|
||||
$("popularity").textContent = data.popularity.toFixed(2);
|
||||
document.getElementById("popularity").textContent = data.popularity.toFixed(2);
|
||||
|
||||
$("reannounce").textContent = window.qBittorrent.Misc.friendlyDuration(data.reannounce);
|
||||
document.getElementById("reannounce").textContent = window.qBittorrent.Misc.friendlyDuration(data.reannounce);
|
||||
|
||||
const lastSeen = (data.last_seen >= 0)
|
||||
? new Date(data.last_seen * 1000).toLocaleString()
|
||||
: "QBT_TR(Never)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||
$("last_seen").textContent = lastSeen;
|
||||
document.getElementById("last_seen").textContent = lastSeen;
|
||||
|
||||
const totalSize = (data.total_size >= 0) ? window.qBittorrent.Misc.friendlyUnit(data.total_size) : "";
|
||||
$("total_size").textContent = totalSize;
|
||||
document.getElementById("total_size").textContent = totalSize;
|
||||
|
||||
const pieces = (data.pieces_num >= 0)
|
||||
? "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
|
@ -190,40 +190,40 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.piece_size))
|
||||
.replace("%3", data.pieces_have)
|
||||
: "";
|
||||
$("pieces").textContent = pieces;
|
||||
document.getElementById("pieces").textContent = pieces;
|
||||
|
||||
$("created_by").textContent = data.created_by;
|
||||
document.getElementById("created_by").textContent = data.created_by;
|
||||
|
||||
const additionDate = (data.addition_date >= 0)
|
||||
? new Date(data.addition_date * 1000).toLocaleString()
|
||||
: "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
|
||||
$("addition_date").textContent = additionDate;
|
||||
document.getElementById("addition_date").textContent = additionDate;
|
||||
|
||||
const completionDate = (data.completion_date >= 0)
|
||||
? new Date(data.completion_date * 1000).toLocaleString()
|
||||
: "";
|
||||
$("completion_date").textContent = completionDate;
|
||||
document.getElementById("completion_date").textContent = completionDate;
|
||||
|
||||
const creationDate = (data.creation_date >= 0)
|
||||
? new Date(data.creation_date * 1000).toLocaleString()
|
||||
: "";
|
||||
$("creation_date").textContent = creationDate;
|
||||
document.getElementById("creation_date").textContent = creationDate;
|
||||
|
||||
const torrentHashV1 = (data.infohash_v1 !== "")
|
||||
? data.infohash_v1
|
||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||
$("torrent_hash_v1").textContent = torrentHashV1;
|
||||
document.getElementById("torrent_hash_v1").textContent = torrentHashV1;
|
||||
|
||||
const torrentHashV2 = (data.infohash_v2 !== "")
|
||||
? data.infohash_v2
|
||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||
$("torrent_hash_v2").textContent = torrentHashV2;
|
||||
document.getElementById("torrent_hash_v2").textContent = torrentHashV2;
|
||||
|
||||
$("save_path").textContent = data.save_path;
|
||||
document.getElementById("save_path").textContent = data.save_path;
|
||||
|
||||
$("comment").innerHTML = window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment));
|
||||
document.getElementById("comment").innerHTML = window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment));
|
||||
|
||||
$("private").textContent = (data.has_metadata
|
||||
document.getElementById("private").textContent = (data.has_metadata
|
||||
? (data.private
|
||||
? "QBT_TR(Yes)QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
: "QBT_TR(No)QBT_TR[CONTEXT=PropertiesWidget]")
|
||||
|
@ -246,13 +246,13 @@ window.qBittorrent.PropGeneral ??= (() => {
|
|||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
$("error_div").textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
document.getElementById("error_div").textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
clearTimeout(loadTorrentDataTimer);
|
||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||
return;
|
||||
}
|
||||
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
|
||||
const data = await response.json();
|
||||
if (data)
|
||||
|
|
|
@ -45,8 +45,8 @@ window.qBittorrent.PropPeers ??= (() => {
|
|||
const loadTorrentPeersData = () => {
|
||||
if (document.hidden)
|
||||
return;
|
||||
if ($("propPeers").classList.contains("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
if (document.getElementById("propPeers").classList.contains("invisible")
|
||||
|| document.getElementById("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
syncTorrentPeersLastResponseId = 0;
|
||||
torrentPeersTable.clear();
|
||||
return;
|
||||
|
@ -73,7 +73,7 @@ window.qBittorrent.PropPeers ??= (() => {
|
|||
|
||||
const responseJSON = await response.json();
|
||||
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
if (responseJSON) {
|
||||
const full_update = (responseJSON["full_update"] === true);
|
||||
if (full_update)
|
||||
|
|
|
@ -45,8 +45,8 @@ window.qBittorrent.PropTrackers ??= (() => {
|
|||
const loadTrackersData = () => {
|
||||
if (document.hidden)
|
||||
return;
|
||||
if ($("propTrackers").classList.contains("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
if (document.getElementById("propTrackers").classList.contains("invisible")
|
||||
|| document.getElementById("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ window.qBittorrent.PropWebseeds ??= (() => {
|
|||
const loadWebSeedsData = () => {
|
||||
if (document.hidden)
|
||||
return;
|
||||
if ($("propWebSeeds").classList.contains("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
if (document.getElementById("propWebSeeds").classList.contains("invisible")
|
||||
|| document.getElementById("propertiesPanel_collapseToggle").classList.contains("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ window.qBittorrent.MultiRename ??= (() => {
|
|||
|
||||
// Search Options
|
||||
_inner_search: "",
|
||||
setSearch(val) {
|
||||
setSearch: function(val) {
|
||||
this._inner_search = val;
|
||||
this._inner_update();
|
||||
this.onChanged(this.matchedFiles);
|
||||
|
@ -33,7 +33,7 @@ window.qBittorrent.MultiRename ??= (() => {
|
|||
|
||||
// Replacement Options
|
||||
_inner_replacement: "",
|
||||
setReplacement(val) {
|
||||
setReplacement: function(val) {
|
||||
this._inner_replacement = val;
|
||||
this._inner_update();
|
||||
this.onChanged(this.matchedFiles);
|
||||
|
|
|
@ -107,7 +107,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
const init = () => {
|
||||
// load "Search in" preference from local storage
|
||||
$("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere";
|
||||
document.getElementById("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere";
|
||||
const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||
targets: "#searchResultsTableDiv tbody tr",
|
||||
menu: "searchResultsTableMenu",
|
||||
|
@ -128,12 +128,12 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
// listen for changes to searchInNameFilter
|
||||
let searchInNameFilterTimer = -1;
|
||||
$("searchInNameFilter").addEventListener("input", () => {
|
||||
document.getElementById("searchInNameFilter").addEventListener("input", (event) => {
|
||||
clearTimeout(searchInNameFilterTimer);
|
||||
searchInNameFilterTimer = setTimeout(() => {
|
||||
searchInNameFilterTimer = -1;
|
||||
|
||||
const value = $("searchInNameFilter").value;
|
||||
const value = document.getElementById("searchInNameFilter").value;
|
||||
searchText.filterPattern = value;
|
||||
searchFilterChanged();
|
||||
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY);
|
||||
|
@ -200,15 +200,15 @@ window.qBittorrent.Search ??= (() => {
|
|||
document.getElementById("startSearchButton").lastChild.textContent = "QBT_TR(Search)QBT_TR[CONTEXT=SearchEngineWidget]";
|
||||
});
|
||||
listItem.appendChild(tabElem);
|
||||
$("searchTabs").appendChild(listItem);
|
||||
document.getElementById("searchTabs").appendChild(listItem);
|
||||
searchResultsTabsContextMenu.addTarget(listItem);
|
||||
|
||||
// unhide the results elements
|
||||
if (numSearchTabs() >= 1) {
|
||||
$("searchResultsNoSearches").classList.add("invisible");
|
||||
$("searchResultsFilters").classList.remove("invisible");
|
||||
$("searchResultsTableContainer").classList.remove("invisible");
|
||||
$("searchTabsToolbar").classList.remove("invisible");
|
||||
document.getElementById("searchResultsNoSearches").classList.add("invisible");
|
||||
document.getElementById("searchResultsFilters").classList.remove("invisible");
|
||||
document.getElementById("searchResultsTableContainer").classList.remove("invisible");
|
||||
document.getElementById("searchTabsToolbar").classList.remove("invisible");
|
||||
}
|
||||
|
||||
// select new tab
|
||||
|
@ -248,7 +248,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
if (state && state.running)
|
||||
stopSearch(searchId);
|
||||
|
||||
tab.destroy();
|
||||
tab.remove();
|
||||
|
||||
fetch("api/v2/search/delete", {
|
||||
method: "POST",
|
||||
|
@ -268,12 +268,12 @@ window.qBittorrent.Search ??= (() => {
|
|||
resetSearchState();
|
||||
resetFilters();
|
||||
|
||||
$("numSearchResultsVisible").textContent = 0;
|
||||
$("numSearchResultsTotal").textContent = 0;
|
||||
$("searchResultsNoSearches").classList.remove("invisible");
|
||||
$("searchResultsFilters").classList.add("invisible");
|
||||
$("searchResultsTableContainer").classList.add("invisible");
|
||||
$("searchTabsToolbar").classList.add("invisible");
|
||||
document.getElementById("numSearchResultsVisible").textContent = 0;
|
||||
document.getElementById("numSearchResultsTotal").textContent = 0;
|
||||
document.getElementById("searchResultsNoSearches").classList.remove("invisible");
|
||||
document.getElementById("searchResultsFilters").classList.add("invisible");
|
||||
document.getElementById("searchResultsTableContainer").classList.add("invisible");
|
||||
document.getElementById("searchTabsToolbar").classList.add("invisible");
|
||||
}
|
||||
else if (isTabSelected && newTabToSelect) {
|
||||
setActiveTab(newTabToSelect);
|
||||
|
@ -335,23 +335,23 @@ window.qBittorrent.Search ??= (() => {
|
|||
// restore filters
|
||||
searchText.pattern = state.searchPattern;
|
||||
searchText.filterPattern = state.filterPattern;
|
||||
$("searchInNameFilter").value = state.filterPattern;
|
||||
document.getElementById("searchInNameFilter").value = state.filterPattern;
|
||||
|
||||
searchSeedsFilter.min = state.seedsFilter.min;
|
||||
searchSeedsFilter.max = state.seedsFilter.max;
|
||||
$("searchMinSeedsFilter").value = state.seedsFilter.min;
|
||||
$("searchMaxSeedsFilter").value = state.seedsFilter.max;
|
||||
document.getElementById("searchMinSeedsFilter").value = state.seedsFilter.min;
|
||||
document.getElementById("searchMaxSeedsFilter").value = state.seedsFilter.max;
|
||||
|
||||
searchSizeFilter.min = state.sizeFilter.min;
|
||||
searchSizeFilter.minUnit = state.sizeFilter.minUnit;
|
||||
searchSizeFilter.max = state.sizeFilter.max;
|
||||
searchSizeFilter.maxUnit = state.sizeFilter.maxUnit;
|
||||
$("searchMinSizeFilter").value = state.sizeFilter.min;
|
||||
$("searchMinSizePrefix").value = state.sizeFilter.minUnit;
|
||||
$("searchMaxSizeFilter").value = state.sizeFilter.max;
|
||||
$("searchMaxSizePrefix").value = state.sizeFilter.maxUnit;
|
||||
document.getElementById("searchMinSizeFilter").value = state.sizeFilter.min;
|
||||
document.getElementById("searchMinSizePrefix").value = state.sizeFilter.minUnit;
|
||||
document.getElementById("searchMaxSizeFilter").value = state.sizeFilter.max;
|
||||
document.getElementById("searchMaxSizePrefix").value = state.sizeFilter.maxUnit;
|
||||
|
||||
const currentSearchPattern = $("searchPattern").value.trim();
|
||||
const currentSearchPattern = document.getElementById("searchPattern").value.trim();
|
||||
if (state.running && (state.searchPattern === currentSearchPattern)) {
|
||||
// allow search to be stopped
|
||||
document.getElementById("startSearchButton").lastChild.textContent = "QBT_TR(Stop)QBT_TR[CONTEXT=SearchEngineWidget]";
|
||||
|
@ -360,7 +360,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
searchResultsTable.setSortedColumn(state.sort.column, state.sort.reverse);
|
||||
|
||||
$("searchInTorrentName").value = state.searchIn;
|
||||
document.getElementById("searchInTorrentName").value = state.searchIn;
|
||||
}
|
||||
|
||||
// must restore all filters before calling updateTable
|
||||
|
@ -370,8 +370,8 @@ window.qBittorrent.Search ??= (() => {
|
|||
if (rowsToSelect.length > 0)
|
||||
searchResultsTable.reselectRows(rowsToSelect);
|
||||
|
||||
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
$("numSearchResultsTotal").textContent = searchResultsTable.getRowSize();
|
||||
document.getElementById("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
document.getElementById("numSearchResultsTotal").textContent = searchResultsTable.getRowSize();
|
||||
};
|
||||
|
||||
const getStatusIconElement = (text, image) => {
|
||||
|
@ -386,7 +386,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
};
|
||||
|
||||
const updateStatusIconElement = (searchId, text, image) => {
|
||||
const searchTab = $(`${searchTabIdPrefix}${searchId}`);
|
||||
const searchTab = document.getElementById(`${searchTabIdPrefix}${searchId}`);
|
||||
if (searchTab) {
|
||||
const statusIcon = searchTab.querySelector(".statusIcon");
|
||||
statusIcon.alt = text;
|
||||
|
@ -439,7 +439,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
};
|
||||
|
||||
const getSelectedSearchId = () => {
|
||||
const selectedTab = $("searchTabs").querySelector("li.selected");
|
||||
const selectedTab = document.getElementById("searchTabs").querySelector("li.selected");
|
||||
return selectedTab ? getSearchIdFromTab(selectedTab) : null;
|
||||
};
|
||||
|
||||
|
@ -448,9 +448,9 @@ window.qBittorrent.Search ??= (() => {
|
|||
const state = searchState.get(currentSearchId);
|
||||
const isSearchRunning = state && state.running;
|
||||
if (!isSearchRunning || searchPatternChanged) {
|
||||
const pattern = $("searchPattern").value.trim();
|
||||
const category = $("categorySelect").value;
|
||||
const plugins = $("pluginsSelect").value;
|
||||
const pattern = document.getElementById("searchPattern").value.trim();
|
||||
const category = document.getElementById("categorySelect").value;
|
||||
const plugins = document.getElementById("pluginsSelect").value;
|
||||
|
||||
if (!pattern || !category || !plugins)
|
||||
return;
|
||||
|
@ -506,7 +506,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
const manageSearchPlugins = () => {
|
||||
const id = "searchPlugins";
|
||||
if (!$(id)) {
|
||||
if (!document.getElementById(id)) {
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: "QBT_TR(Search plugins)QBT_TR[CONTEXT=PluginSelectDlg]",
|
||||
|
@ -541,7 +541,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
const onSearchPatternChanged = () => {
|
||||
const currentSearchId = getSelectedSearchId();
|
||||
const state = searchState.get(currentSearchId);
|
||||
const currentSearchPattern = $("searchPattern").value.trim();
|
||||
const currentSearchPattern = document.getElementById("searchPattern").value.trim();
|
||||
// start a new search if pattern has changed, otherwise allow the search to be stopped
|
||||
if (state && (state.searchPattern === currentSearchPattern)) {
|
||||
searchPatternChanged = false;
|
||||
|
@ -554,11 +554,11 @@ window.qBittorrent.Search ??= (() => {
|
|||
};
|
||||
|
||||
const categorySelected = () => {
|
||||
selectedCategory = $("categorySelect").value;
|
||||
selectedCategory = document.getElementById("categorySelect").value;
|
||||
};
|
||||
|
||||
const pluginSelected = () => {
|
||||
selectedPlugin = $("pluginsSelect").value;
|
||||
selectedPlugin = document.getElementById("pluginsSelect").value;
|
||||
|
||||
if (selectedPlugin !== prevSelectedPlugin) {
|
||||
prevSelectedPlugin = selectedPlugin;
|
||||
|
@ -567,18 +567,18 @@ window.qBittorrent.Search ??= (() => {
|
|||
};
|
||||
|
||||
const reselectCategory = () => {
|
||||
for (let i = 0; i < $("categorySelect").options.length; ++i) {
|
||||
if ($("categorySelect").options[i].get("value") === selectedCategory)
|
||||
$("categorySelect").options[i].selected = true;
|
||||
for (let i = 0; i < document.getElementById("categorySelect").options.length; ++i) {
|
||||
if (document.getElementById("categorySelect").options[i].get("value") === selectedCategory)
|
||||
document.getElementById("categorySelect").options[i].selected = true;
|
||||
}
|
||||
|
||||
categorySelected();
|
||||
};
|
||||
|
||||
const reselectPlugin = () => {
|
||||
for (let i = 0; i < $("pluginsSelect").options.length; ++i) {
|
||||
if ($("pluginsSelect").options[i].get("value") === selectedPlugin)
|
||||
$("pluginsSelect").options[i].selected = true;
|
||||
for (let i = 0; i < document.getElementById("pluginsSelect").options.length; ++i) {
|
||||
if (document.getElementById("pluginsSelect").options[i].get("value") === selectedPlugin)
|
||||
document.getElementById("pluginsSelect").options[i].selected = true;
|
||||
}
|
||||
|
||||
pluginSelected();
|
||||
|
@ -603,7 +603,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
option.value = category.id;
|
||||
option.textContent = category.name;
|
||||
categoryOptions.push(option);
|
||||
};
|
||||
}
|
||||
|
||||
// first category is "All Categories"
|
||||
if (categoryOptions.length > 1) {
|
||||
|
@ -614,10 +614,10 @@ window.qBittorrent.Search ??= (() => {
|
|||
categoryOptions.splice(1, 0, option);
|
||||
}
|
||||
|
||||
$("categorySelect").replaceChildren(...categoryOptions);
|
||||
document.getElementById("categorySelect").replaceChildren(...categoryOptions);
|
||||
};
|
||||
|
||||
const selectedPlugin = $("pluginsSelect").value;
|
||||
const selectedPlugin = document.getElementById("pluginsSelect").value;
|
||||
|
||||
if ((selectedPlugin === "all") || (selectedPlugin === "enabled")) {
|
||||
const uniqueCategories = {};
|
||||
|
@ -675,9 +675,9 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
const searchPluginsEmpty = (searchPlugins.length === 0);
|
||||
if (!searchPluginsEmpty) {
|
||||
$("searchResultsNoPlugins").classList.add("invisible");
|
||||
document.getElementById("searchResultsNoPlugins").classList.add("invisible");
|
||||
if (numSearchTabs() === 0)
|
||||
$("searchResultsNoSearches").classList.remove("invisible");
|
||||
document.getElementById("searchResultsNoSearches").classList.remove("invisible");
|
||||
|
||||
// sort plugins alphabetically
|
||||
const allPlugins = searchPlugins.sort((left, right) => {
|
||||
|
@ -695,11 +695,11 @@ window.qBittorrent.Search ??= (() => {
|
|||
pluginOptions.splice(2, 0, createOption("──────────", undefined, true));
|
||||
}
|
||||
|
||||
$("pluginsSelect").replaceChildren(...pluginOptions);
|
||||
document.getElementById("pluginsSelect").replaceChildren(...pluginOptions);
|
||||
|
||||
$("searchPattern").disabled = searchPluginsEmpty;
|
||||
$("categorySelect").disabled = searchPluginsEmpty;
|
||||
$("pluginsSelect").disabled = searchPluginsEmpty;
|
||||
document.getElementById("searchPattern").disabled = searchPluginsEmpty;
|
||||
document.getElementById("categorySelect").disabled = searchPluginsEmpty;
|
||||
document.getElementById("pluginsSelect").disabled = searchPluginsEmpty;
|
||||
document.getElementById("startSearchButton").disabled = searchPluginsEmpty;
|
||||
|
||||
if (window.qBittorrent.SearchPlugins !== undefined)
|
||||
|
@ -721,25 +721,25 @@ window.qBittorrent.Search ??= (() => {
|
|||
|
||||
const resetFilters = () => {
|
||||
searchText.filterPattern = "";
|
||||
$("searchInNameFilter").value = "";
|
||||
document.getElementById("searchInNameFilter").value = "";
|
||||
|
||||
searchSeedsFilter.min = 0;
|
||||
searchSeedsFilter.max = 0;
|
||||
$("searchMinSeedsFilter").value = searchSeedsFilter.min;
|
||||
$("searchMaxSeedsFilter").value = searchSeedsFilter.max;
|
||||
document.getElementById("searchMinSeedsFilter").value = searchSeedsFilter.min;
|
||||
document.getElementById("searchMaxSeedsFilter").value = searchSeedsFilter.max;
|
||||
|
||||
searchSizeFilter.min = 0.00;
|
||||
searchSizeFilter.minUnit = 2; // B = 0, KiB = 1, MiB = 2, GiB = 3, TiB = 4, PiB = 5, EiB = 6
|
||||
searchSizeFilter.max = 0.00;
|
||||
searchSizeFilter.maxUnit = 3;
|
||||
$("searchMinSizeFilter").value = searchSizeFilter.min;
|
||||
$("searchMinSizePrefix").value = searchSizeFilter.minUnit;
|
||||
$("searchMaxSizeFilter").value = searchSizeFilter.max;
|
||||
$("searchMaxSizePrefix").value = searchSizeFilter.maxUnit;
|
||||
document.getElementById("searchMinSizeFilter").value = searchSizeFilter.min;
|
||||
document.getElementById("searchMinSizePrefix").value = searchSizeFilter.minUnit;
|
||||
document.getElementById("searchMaxSizeFilter").value = searchSizeFilter.max;
|
||||
document.getElementById("searchMaxSizePrefix").value = searchSizeFilter.maxUnit;
|
||||
};
|
||||
|
||||
const getSearchInTorrentName = () => {
|
||||
return ($("searchInTorrentName").value === "names") ? "names" : "everywhere";
|
||||
return (document.getElementById("searchInTorrentName").value === "names") ? "names" : "everywhere";
|
||||
};
|
||||
|
||||
const searchInTorrentName = () => {
|
||||
|
@ -748,29 +748,29 @@ window.qBittorrent.Search ??= (() => {
|
|||
};
|
||||
|
||||
const searchSeedsFilterChanged = () => {
|
||||
searchSeedsFilter.min = $("searchMinSeedsFilter").value;
|
||||
searchSeedsFilter.max = $("searchMaxSeedsFilter").value;
|
||||
searchSeedsFilter.min = document.getElementById("searchMinSeedsFilter").value;
|
||||
searchSeedsFilter.max = document.getElementById("searchMaxSeedsFilter").value;
|
||||
|
||||
searchFilterChanged();
|
||||
};
|
||||
|
||||
const searchSizeFilterChanged = () => {
|
||||
searchSizeFilter.min = $("searchMinSizeFilter").value;
|
||||
searchSizeFilter.minUnit = $("searchMinSizePrefix").value;
|
||||
searchSizeFilter.max = $("searchMaxSizeFilter").value;
|
||||
searchSizeFilter.maxUnit = $("searchMaxSizePrefix").value;
|
||||
searchSizeFilter.min = document.getElementById("searchMinSizeFilter").value;
|
||||
searchSizeFilter.minUnit = document.getElementById("searchMinSizePrefix").value;
|
||||
searchSizeFilter.max = document.getElementById("searchMaxSizeFilter").value;
|
||||
searchSizeFilter.maxUnit = document.getElementById("searchMaxSizePrefix").value;
|
||||
|
||||
searchFilterChanged();
|
||||
};
|
||||
|
||||
const searchSizeFilterPrefixChanged = () => {
|
||||
if ((Number($("searchMinSizeFilter").value) !== 0) || (Number($("searchMaxSizeFilter").value) !== 0))
|
||||
if ((Number(document.getElementById("searchMinSizeFilter").value) !== 0) || (Number(document.getElementById("searchMaxSizeFilter").value) !== 0))
|
||||
searchSizeFilterChanged();
|
||||
};
|
||||
|
||||
const searchFilterChanged = () => {
|
||||
searchResultsTable.updateTable();
|
||||
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
document.getElementById("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
};
|
||||
|
||||
const loadSearchResultsData = function(searchId) {
|
||||
|
@ -799,7 +799,7 @@ window.qBittorrent.Search ??= (() => {
|
|||
return;
|
||||
}
|
||||
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
|
||||
const state = searchState.get(searchId);
|
||||
// check if user stopped the search prior to receiving the response
|
||||
|
@ -842,8 +842,8 @@ window.qBittorrent.Search ??= (() => {
|
|||
for (const row of newRows)
|
||||
searchResultsTable.updateRowData(row);
|
||||
|
||||
$("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
$("numSearchResultsTotal").textContent = searchResultsTable.getRowSize();
|
||||
document.getElementById("numSearchResultsVisible").textContent = searchResultsTable.getFilteredAndSortedRows().length;
|
||||
document.getElementById("numSearchResultsTotal").textContent = searchResultsTable.getRowSize();
|
||||
|
||||
searchResultsTable.updateTable();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
MochaUI.extend({
|
||||
addUpLimitSlider: (hashes) => {
|
||||
if ($("uplimitSliderarea")) {
|
||||
if (document.getElementById("uplimitSliderarea")) {
|
||||
// Get global upload limit
|
||||
fetch("api/v2/transfer/uploadLimit", {
|
||||
method: "GET",
|
||||
|
@ -61,29 +61,29 @@ MochaUI.extend({
|
|||
if (up_limit < 0)
|
||||
up_limit = 0;
|
||||
maximum = 10000;
|
||||
new Slider($("uplimitSliderarea"), $("uplimitSliderknob"), {
|
||||
new Slider(document.getElementById("uplimitSliderarea"), document.getElementById("uplimitSliderknob"), {
|
||||
steps: maximum,
|
||||
offset: 0,
|
||||
initialStep: Math.round(up_limit),
|
||||
onChange: (pos) => {
|
||||
if (pos > 0) {
|
||||
$("uplimitUpdatevalue").value = pos;
|
||||
$("upLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("uplimitUpdatevalue").value = pos;
|
||||
document.getElementById("upLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
else {
|
||||
$("uplimitUpdatevalue").value = "∞";
|
||||
$("upLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("uplimitUpdatevalue").value = "∞";
|
||||
document.getElementById("upLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
});
|
||||
// Set default value
|
||||
if (up_limit === 0) {
|
||||
$("uplimitUpdatevalue").value = "∞";
|
||||
$("upLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("uplimitUpdatevalue").value = "∞";
|
||||
document.getElementById("upLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
else {
|
||||
$("uplimitUpdatevalue").value = Math.round(up_limit);
|
||||
$("upLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("uplimitUpdatevalue").value = Math.round(up_limit);
|
||||
document.getElementById("upLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -108,29 +108,29 @@ MochaUI.extend({
|
|||
}
|
||||
if (up_limit < 0)
|
||||
up_limit = 0;
|
||||
new Slider($("uplimitSliderarea"), $("uplimitSliderknob"), {
|
||||
new Slider(document.getElementById("uplimitSliderarea"), document.getElementById("uplimitSliderknob"), {
|
||||
steps: maximum,
|
||||
offset: 0,
|
||||
initialStep: Math.round(up_limit / 1024),
|
||||
onChange: (pos) => {
|
||||
if (pos > 0) {
|
||||
$("uplimitUpdatevalue").value = pos;
|
||||
$("upLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("uplimitUpdatevalue").value = pos;
|
||||
document.getElementById("upLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
else {
|
||||
$("uplimitUpdatevalue").value = "∞";
|
||||
$("upLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("uplimitUpdatevalue").value = "∞";
|
||||
document.getElementById("upLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
});
|
||||
// Set default value
|
||||
if (up_limit === 0) {
|
||||
$("uplimitUpdatevalue").value = "∞";
|
||||
$("upLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("uplimitUpdatevalue").value = "∞";
|
||||
document.getElementById("upLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
else {
|
||||
$("uplimitUpdatevalue").value = Math.round(up_limit / 1024);
|
||||
$("upLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("uplimitUpdatevalue").value = Math.round(up_limit / 1024);
|
||||
document.getElementById("upLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ MochaUI.extend({
|
|||
},
|
||||
|
||||
addDlLimitSlider: (hashes) => {
|
||||
if ($("dllimitSliderarea")) {
|
||||
if (document.getElementById("dllimitSliderarea")) {
|
||||
// Get global upload limit
|
||||
fetch("api/v2/transfer/downloadLimit", {
|
||||
method: "GET",
|
||||
|
@ -170,29 +170,29 @@ MochaUI.extend({
|
|||
if (dl_limit < 0)
|
||||
dl_limit = 0;
|
||||
maximum = 10000;
|
||||
new Slider($("dllimitSliderarea"), $("dllimitSliderknob"), {
|
||||
new Slider(document.getElementById("dllimitSliderarea"), document.getElementById("dllimitSliderknob"), {
|
||||
steps: maximum,
|
||||
offset: 0,
|
||||
initialStep: Math.round(dl_limit),
|
||||
onChange: (pos) => {
|
||||
if (pos > 0) {
|
||||
$("dllimitUpdatevalue").value = pos;
|
||||
$("dlLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("dllimitUpdatevalue").value = pos;
|
||||
document.getElementById("dlLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
else {
|
||||
$("dllimitUpdatevalue").value = "∞";
|
||||
$("dlLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("dllimitUpdatevalue").value = "∞";
|
||||
document.getElementById("dlLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
});
|
||||
// Set default value
|
||||
if (dl_limit === 0) {
|
||||
$("dllimitUpdatevalue").value = "∞";
|
||||
$("dlLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("dllimitUpdatevalue").value = "∞";
|
||||
document.getElementById("dlLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
else {
|
||||
$("dllimitUpdatevalue").value = Math.round(dl_limit);
|
||||
$("dlLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("dllimitUpdatevalue").value = Math.round(dl_limit);
|
||||
document.getElementById("dlLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -217,29 +217,29 @@ MochaUI.extend({
|
|||
}
|
||||
if (dl_limit < 0)
|
||||
dl_limit = 0;
|
||||
new Slider($("dllimitSliderarea"), $("dllimitSliderknob"), {
|
||||
new Slider(document.getElementById("dllimitSliderarea"), document.getElementById("dllimitSliderknob"), {
|
||||
steps: maximum,
|
||||
offset: 0,
|
||||
initialStep: Math.round(dl_limit / 1024),
|
||||
onChange: (pos) => {
|
||||
if (pos > 0) {
|
||||
$("dllimitUpdatevalue").value = pos;
|
||||
$("dlLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("dllimitUpdatevalue").value = pos;
|
||||
document.getElementById("dlLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
else {
|
||||
$("dllimitUpdatevalue").value = "∞";
|
||||
$("dlLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("dllimitUpdatevalue").value = "∞";
|
||||
document.getElementById("dlLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
});
|
||||
// Set default value
|
||||
if (dl_limit === 0) {
|
||||
$("dllimitUpdatevalue").value = "∞";
|
||||
$("dlLimitUnit").style.visibility = "hidden";
|
||||
document.getElementById("dllimitUpdatevalue").value = "∞";
|
||||
document.getElementById("dlLimitUnit").style.visibility = "hidden";
|
||||
}
|
||||
else {
|
||||
$("dllimitUpdatevalue").value = Math.round(dl_limit / 1024);
|
||||
$("dlLimitUnit").style.visibility = "visible";
|
||||
document.getElementById("dllimitUpdatevalue").value = Math.round(dl_limit / 1024);
|
||||
document.getElementById("dlLimitUnit").style.visibility = "visible";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
<script>
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("setLocationButton").click();
|
||||
document.getElementById("setLocationButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -32,17 +32,17 @@
|
|||
|
||||
// set text field to current value
|
||||
if (path !== null)
|
||||
$("setLocation").value = decodeURIComponent(path);
|
||||
document.getElementById("setLocation").value = decodeURIComponent(path);
|
||||
|
||||
$("setLocation").focus();
|
||||
$("setLocationButton").addEventListener("click", (e) => {
|
||||
document.getElementById("setLocation").focus();
|
||||
document.getElementById("setLocationButton").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// check field
|
||||
const location = $("setLocation").value.trim();
|
||||
const location = document.getElementById("setLocation").value.trim();
|
||||
if ((location === null) || (location === "")) {
|
||||
$("error_div").textContent = "QBT_TR(Save path is empty)QBT_TR[CONTEXT=TorrentsController]";
|
||||
document.getElementById("error_div").textContent = "QBT_TR(Save path is empty)QBT_TR[CONTEXT=TorrentsController]";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
|||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
$("error_div").textContent = await response.text();
|
||||
document.getElementById("error_div").textContent = await response.text();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
const UseGlobalLimit = -2;
|
||||
const NoLimit = -1;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("save").click();
|
||||
document.getElementById("save").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -56,23 +56,23 @@
|
|||
else {
|
||||
setSelectedRadioValue("shareLimit", "custom");
|
||||
if (values.ratioLimit >= 0) {
|
||||
$("setRatio").checked = true;
|
||||
$("ratio").value = values.ratioLimit.toFixed(2);
|
||||
document.getElementById("setRatio").checked = true;
|
||||
document.getElementById("ratio").value = values.ratioLimit.toFixed(2);
|
||||
}
|
||||
if (values.seedingTimeLimit >= 0) {
|
||||
$("setTotalMinutes").checked = true;
|
||||
$("totalMinutes").value = values.seedingTimeLimit;
|
||||
document.getElementById("setTotalMinutes").checked = true;
|
||||
document.getElementById("totalMinutes").value = values.seedingTimeLimit;
|
||||
}
|
||||
if (values.inactiveSeedingTimeLimit >= 0) {
|
||||
$("setInactiveMinutes").checked = true;
|
||||
$("inactiveMinutes").value = values.inactiveSeedingTimeLimit;
|
||||
document.getElementById("setInactiveMinutes").checked = true;
|
||||
document.getElementById("inactiveMinutes").value = values.inactiveSeedingTimeLimit;
|
||||
}
|
||||
}
|
||||
|
||||
shareLimitChanged();
|
||||
|
||||
$("default").focus();
|
||||
$("save").addEventListener("click", (e) => {
|
||||
document.getElementById("default").focus();
|
||||
document.getElementById("save").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -91,9 +91,9 @@
|
|||
ratioLimitValue = seedingTimeLimitValue = inactiveSeedingTimeLimitValue = NoLimit;
|
||||
}
|
||||
else if (shareLimit === "custom") {
|
||||
ratioLimitValue = $("setRatio").checked ? $("ratio").value : -1;
|
||||
seedingTimeLimitValue = $("setTotalMinutes").checked ? $("totalMinutes").value : -1;
|
||||
inactiveSeedingTimeLimitValue = $("setInactiveMinutes").checked ? $("inactiveMinutes").value : -1;
|
||||
ratioLimitValue = document.getElementById("setRatio").checked ? document.getElementById("ratio").value : -1;
|
||||
seedingTimeLimitValue = document.getElementById("setTotalMinutes").checked ? document.getElementById("totalMinutes").value : -1;
|
||||
inactiveSeedingTimeLimitValue = document.getElementById("setInactiveMinutes").checked ? document.getElementById("inactiveMinutes").value : -1;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
|
@ -108,7 +108,7 @@
|
|||
inactiveSeedingTimeLimit: inactiveSeedingTimeLimitValue
|
||||
})
|
||||
})
|
||||
.then(async (response) => {
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
return;
|
||||
|
||||
|
@ -139,26 +139,26 @@
|
|||
|
||||
const shareLimitChanged = () => {
|
||||
const customShareLimit = getSelectedRadioValue("shareLimit") === "custom";
|
||||
$("setRatio").disabled = !customShareLimit;
|
||||
$("setTotalMinutes").disabled = !customShareLimit;
|
||||
$("setInactiveMinutes").disabled = !customShareLimit;
|
||||
document.getElementById("setRatio").disabled = !customShareLimit;
|
||||
document.getElementById("setTotalMinutes").disabled = !customShareLimit;
|
||||
document.getElementById("setInactiveMinutes").disabled = !customShareLimit;
|
||||
|
||||
enableInputBoxes();
|
||||
|
||||
$("save").disabled = !isFormValid();
|
||||
document.getElementById("save").disabled = !isFormValid();
|
||||
};
|
||||
|
||||
const enableInputBoxes = () => {
|
||||
$("ratio").disabled = $("setRatio").disabled || !$("setRatio").checked;
|
||||
$("totalMinutes").disabled = $("setTotalMinutes").disabled || !$("setTotalMinutes").checked;
|
||||
$("inactiveMinutes").disabled = $("setInactiveMinutes").disabled || !$("setInactiveMinutes").checked;
|
||||
document.getElementById("ratio").disabled = document.getElementById("setRatio").disabled || !document.getElementById("setRatio").checked;
|
||||
document.getElementById("totalMinutes").disabled = document.getElementById("setTotalMinutes").disabled || !document.getElementById("setTotalMinutes").checked;
|
||||
document.getElementById("inactiveMinutes").disabled = document.getElementById("setInactiveMinutes").disabled || !document.getElementById("setInactiveMinutes").checked;
|
||||
|
||||
$("save").disabled = !isFormValid();
|
||||
document.getElementById("save").disabled = !isFormValid();
|
||||
};
|
||||
|
||||
const isFormValid = () => {
|
||||
return !((getSelectedRadioValue("shareLimit") === "custom") && !$("setRatio").checked
|
||||
&& !$("setTotalMinutes").checked && !$("setInactiveMinutes").checked);
|
||||
return !((getSelectedRadioValue("shareLimit") === "custom") && !document.getElementById("setRatio").checked
|
||||
&& !document.getElementById("setTotalMinutes").checked && !document.getElementById("setInactiveMinutes").checked);
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
@ -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">
|
||||
|
|
|
@ -160,23 +160,23 @@
|
|||
|
||||
let submitted = false;
|
||||
|
||||
$("uploadForm").addEventListener("submit", () => {
|
||||
$("startTorrentHidden").value = $("startTorrent").checked ? "false" : "true";
|
||||
document.getElementById("uploadForm").addEventListener("submit", (event) => {
|
||||
document.getElementById("startTorrentHidden").value = document.getElementById("startTorrent").checked ? "false" : "true";
|
||||
|
||||
$("dlLimitHidden").value = Number($("dlLimitText").value) * 1024;
|
||||
$("upLimitHidden").value = Number($("upLimitText").value) * 1024;
|
||||
document.getElementById("dlLimitHidden").value = Number(document.getElementById("dlLimitText").value) * 1024;
|
||||
document.getElementById("upLimitHidden").value = Number(document.getElementById("upLimitText").value) * 1024;
|
||||
|
||||
$("upload_spinner").style.display = "block";
|
||||
document.getElementById("upload_spinner").style.display = "block";
|
||||
submitted = true;
|
||||
});
|
||||
|
||||
$("upload_frame").addEventListener("load", () => {
|
||||
document.getElementById("upload_frame").addEventListener("load", (event) => {
|
||||
if (submitted)
|
||||
window.parent.qBittorrent.Client.closeFrameWindow(window);
|
||||
});
|
||||
|
||||
if ((Browser.platform === "ios") || ((Browser.platform === "mac") && (navigator.maxTouchPoints > 1)))
|
||||
$("fileselect").accept = ".torrent";
|
||||
document.getElementById("fileselect").accept = ".torrent";
|
||||
|
||||
window.qBittorrent.pathAutofill.attachPathAutofill();
|
||||
</script>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
switch (event.key) {
|
||||
case "Enter":
|
||||
event.preventDefault();
|
||||
$("applyButton").click();
|
||||
document.getElementById("applyButton").click();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
|
@ -48,7 +48,7 @@
|
|||
|
||||
const hashes = new URLSearchParams(window.location.search).get("hashes").split("|");
|
||||
const setUpLimit = () => {
|
||||
const limit = Number($("uplimitUpdatevalue").value) * 1024;
|
||||
const limit = Number(document.getElementById("uplimitUpdatevalue").value) * 1024;
|
||||
if (hashes[0] === "global") {
|
||||
fetch("api/v2/transfer/setUploadLimit", {
|
||||
method: "POST",
|
||||
|
@ -81,7 +81,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
$("uplimitUpdatevalue").focus();
|
||||
document.getElementById("uplimitUpdatevalue").focus();
|
||||
|
||||
MochaUI.addUpLimitSlider(hashes);
|
||||
</script>
|
||||
|
|
|
@ -855,12 +855,12 @@
|
|||
const qbtVersion = window.parent.qBittorrent.Cache.qbtVersion.get();
|
||||
const buildInfo = window.parent.qBittorrent.Cache.buildInfo.get();
|
||||
|
||||
$("qbittorrentVersion").textContent = `qBittorrent ${qbtVersion} QBT_TR(WebUI)QBT_TR[CONTEXT=OptionsDialog]`;
|
||||
$("qtVersion").textContent = buildInfo.qt;
|
||||
$("libtorrentVersion").textContent = buildInfo.libtorrent;
|
||||
$("boostVersion").textContent = buildInfo.boost;
|
||||
$("opensslVersion").textContent = buildInfo.openssl;
|
||||
$("zlibVersion").textContent = buildInfo.zlib;
|
||||
$("qbittorrentVersion").textContent += ` (${buildInfo.bitness}-bit)`;
|
||||
document.getElementById("qbittorrentVersion").textContent = `qBittorrent ${qbtVersion} QBT_TR(WebUI)QBT_TR[CONTEXT=OptionsDialog]`;
|
||||
document.getElementById("qtVersion").textContent = buildInfo.qt;
|
||||
document.getElementById("libtorrentVersion").textContent = buildInfo.libtorrent;
|
||||
document.getElementById("boostVersion").textContent = buildInfo.boost;
|
||||
document.getElementById("opensslVersion").textContent = buildInfo.openssl;
|
||||
document.getElementById("zlibVersion").textContent = buildInfo.zlib;
|
||||
document.getElementById("qbittorrentVersion").textContent += ` (${buildInfo.bitness}-bit)`;
|
||||
})();
|
||||
</script>
|
||||
|
|
|
@ -16,34 +16,34 @@
|
|||
(() => {
|
||||
MochaUI.initializeTabs("aboutTabs");
|
||||
|
||||
$("aboutAboutLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutAboutLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutAboutContent").classList.remove("invisible");
|
||||
document.getElementById("aboutAboutContent").classList.remove("invisible");
|
||||
});
|
||||
|
||||
$("aboutAuthorLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutAuthorLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutAuthorContent").classList.remove("invisible");
|
||||
document.getElementById("aboutAuthorContent").classList.remove("invisible");
|
||||
});
|
||||
|
||||
$("aboutSpecialThanksLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutSpecialThanksLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutSpecialThanksContent").classList.remove("invisible");
|
||||
document.getElementById("aboutSpecialThanksContent").classList.remove("invisible");
|
||||
});
|
||||
|
||||
$("aboutTranslatorsLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutTranslatorsLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutTranslatorsContent").classList.remove("invisible");
|
||||
document.getElementById("aboutTranslatorsContent").classList.remove("invisible");
|
||||
});
|
||||
|
||||
$("aboutLicenseLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutLicenseLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutLicenseContent").classList.remove("invisible");
|
||||
document.getElementById("aboutLicenseContent").classList.remove("invisible");
|
||||
});
|
||||
|
||||
$("aboutSoftwareUsedLink").addEventListener("click", () => {
|
||||
document.getElementById("aboutSoftwareUsedLink").addEventListener("click", (event) => {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".aboutTabContent"), (tab => tab.classList.add("invisible")));
|
||||
$("aboutSoftwareUsedContent").classList.remove("invisible");
|
||||
document.getElementById("aboutSoftwareUsedContent").classList.remove("invisible");
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
};
|
||||
|
||||
const deleteCookie = (element) => {
|
||||
element.closest("tr").destroy();
|
||||
element.closest("tr").remove();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
|
|
|
@ -61,11 +61,11 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("newPluginPath").select();
|
||||
document.getElementById("newPluginPath").select();
|
||||
};
|
||||
|
||||
const newPluginOk = () => {
|
||||
const path = $("newPluginPath").value.trim();
|
||||
const path = document.getElementById("newPluginPath").value.trim();
|
||||
if (path) {
|
||||
fetch("api/v2/search/installPlugin", {
|
||||
method: "POST",
|
||||
|
|
|
@ -188,7 +188,7 @@
|
|||
let selectedLogLevels = JSON.parse(LocalPreferences.get("qbt_selected_log_levels")) || ["1", "2", "4", "8"];
|
||||
|
||||
const init = () => {
|
||||
for (const option of $("logLevelSelect").options)
|
||||
for (const option of document.getElementById("logLevelSelect").options)
|
||||
option.toggleAttribute("selected", selectedLogLevels.includes(option.value));
|
||||
|
||||
selectBox = new vanillaSelectBox("#logLevelSelect", {
|
||||
|
@ -282,7 +282,7 @@
|
|||
};
|
||||
|
||||
const filterTextChanged = () => {
|
||||
const value = $("filterTextInput").value.trim();
|
||||
const value = document.getElementById("filterTextInput").value.trim();
|
||||
if (inputtedFilterText !== value) {
|
||||
inputtedFilterText = value;
|
||||
logFilterChanged();
|
||||
|
@ -306,14 +306,14 @@
|
|||
currentSelectedTab = tab;
|
||||
if (currentSelectedTab === "main") {
|
||||
selectBox.enable();
|
||||
$("logMessageView").classList.remove("invisible");
|
||||
$("logPeerView").classList.add("invisible");
|
||||
document.getElementById("logMessageView").classList.remove("invisible");
|
||||
document.getElementById("logPeerView").classList.add("invisible");
|
||||
resetTableTimer("peer");
|
||||
}
|
||||
else {
|
||||
selectBox.disable();
|
||||
$("logMessageView").classList.add("invisible");
|
||||
$("logPeerView").classList.remove("invisible");
|
||||
document.getElementById("logMessageView").classList.add("invisible");
|
||||
document.getElementById("logPeerView").classList.remove("invisible");
|
||||
resetTableTimer("main");
|
||||
}
|
||||
|
||||
|
@ -331,8 +331,8 @@
|
|||
if (curTab === undefined)
|
||||
curTab = currentSelectedTab;
|
||||
|
||||
$("numFilteredLogs").textContent = tableInfo[curTab].instance.filteredLength;
|
||||
$("numTotalLogs").textContent = tableInfo[curTab].instance.getRowSize();
|
||||
document.getElementById("numFilteredLogs").textContent = tableInfo[curTab].instance.filteredLength;
|
||||
document.getElementById("numTotalLogs").textContent = tableInfo[curTab].instance.getRowSize();
|
||||
};
|
||||
|
||||
const syncLogData = (curTab) => {
|
||||
|
@ -368,7 +368,7 @@
|
|||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
const errorDiv = $("error_div");
|
||||
const errorDiv = document.getElementById("error_div");
|
||||
if (errorDiv)
|
||||
errorDiv.textContent = "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]";
|
||||
tableInfo[curTab].progress = false;
|
||||
|
@ -376,9 +376,9 @@
|
|||
return;
|
||||
}
|
||||
|
||||
$("error_div").textContent = "";
|
||||
document.getElementById("error_div").textContent = "";
|
||||
|
||||
if ($("logTabColumn").classList.contains("invisible"))
|
||||
if (document.getElementById("logTabColumn").classList.contains("invisible"))
|
||||
return;
|
||||
|
||||
const responseJSON = await response.json();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue