mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-10-22 10:46:04 +03:00
commit
195eae5f3d
125 changed files with 6093 additions and 5209 deletions
7
.github/workflows/ci_macos.yaml
vendored
7
.github/workflows/ci_macos.yaml
vendored
|
@ -23,7 +23,6 @@ jobs:
|
|||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
openssl_root: "$(brew --prefix openssl@3)"
|
||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||
|
||||
steps:
|
||||
|
@ -70,7 +69,7 @@ jobs:
|
|||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: qtbase qtdeclarative qtsvg qttools
|
||||
|
@ -94,8 +93,7 @@ jobs:
|
|||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
||||
|
@ -109,7 +107,6 @@ jobs:
|
|||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
|
|
12
.github/workflows/ci_python.yaml
vendored
12
.github/workflows/ci_python.yaml
vendored
|
@ -53,7 +53,7 @@ jobs:
|
|||
python-version: '3.7'
|
||||
|
||||
- name: Install tools (search engine)
|
||||
run: pip install bandit pycodestyle pyflakes
|
||||
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||
|
||||
- name: Gather files (search engine)
|
||||
run: |
|
||||
|
@ -61,6 +61,16 @@ jobs:
|
|||
echo $PY_FILES
|
||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check typings (search engine)
|
||||
run: |
|
||||
MYPYPATH="src/searchengine/nova3" \
|
||||
mypy \
|
||||
--follow-imports skip \
|
||||
--strict \
|
||||
$PY_FILES
|
||||
pyright \
|
||||
$PY_FILES
|
||||
|
||||
- name: Lint code (search engine)
|
||||
run: |
|
||||
pyflakes $PY_FILES
|
||||
|
|
14
.github/workflows/ci_ubuntu.yaml
vendored
14
.github/workflows/ci_ubuntu.yaml
vendored
|
@ -64,7 +64,7 @@ jobs:
|
|||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
|
@ -138,12 +138,12 @@ jobs:
|
|||
curl \
|
||||
-L \
|
||||
-Z \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
chmod +x \
|
||||
linuxdeploy-x86_64.AppImage \
|
||||
linuxdeploy-plugin-qt-x86_64.AppImage \
|
||||
linuxdeploy-static-x86_64.AppImage \
|
||||
linuxdeploy-plugin-qt-static-x86_64.AppImage \
|
||||
linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
|
||||
- name: Prepare files for AppImage
|
||||
|
@ -156,12 +156,12 @@ jobs:
|
|||
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
./linuxdeploy-static-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
|
||||
NO_APPSTREAM=1 \
|
||||
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
|
||||
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
30
.github/workflows/ci_windows.yaml
vendored
30
.github/workflows/ci_windows.yaml
vendored
|
@ -93,7 +93,7 @@ jobs:
|
|||
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.7.0"
|
||||
archives: qtbase qtsvg qttools
|
||||
|
@ -153,26 +153,26 @@ jobs:
|
|||
copy build/qbittorrent.pdb upload/qBittorrent
|
||||
copy dist/windows/qt.conf upload/qBittorrent
|
||||
# runtimes
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
mkdir upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt6_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
mkdir upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
mkdir upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt6_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
mkdir upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
mkdir upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||
mkdir upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
# cmake additionals
|
||||
mkdir upload/cmake
|
||||
copy build/compile_commands.json upload/cmake
|
||||
|
|
2
.github/workflows/coverity-scan.yaml
vendored
2
.github/workflows/coverity-scan.yaml
vendored
|
@ -52,7 +52,7 @@ jobs:
|
|||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
|
|
|
@ -38,6 +38,8 @@ add_library(qbt_base STATIC
|
|||
bittorrent/torrent.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcontentremoveoption.h
|
||||
bittorrent/torrentcontentremover.h
|
||||
bittorrent/torrentcreationmanager.h
|
||||
bittorrent/torrentcreationtask.h
|
||||
bittorrent/torrentcreator.h
|
||||
|
@ -145,6 +147,7 @@ add_library(qbt_base STATIC
|
|||
bittorrent/sslparameters.cpp
|
||||
bittorrent/torrent.cpp
|
||||
bittorrent/torrentcontenthandler.cpp
|
||||
bittorrent/torrentcontentremover.cpp
|
||||
bittorrent/torrentcreationmanager.cpp
|
||||
bittorrent/torrentcreationtask.cpp
|
||||
bittorrent/torrentcreator.cpp
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
|
|
|
@ -240,11 +240,11 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
|
|||
|
||||
lt::storage_interface *customStorageConstructor(const lt::storage_params ¶ms, lt::file_pool &pool)
|
||||
{
|
||||
return new CustomStorage {params, pool};
|
||||
return new CustomStorage(params, pool);
|
||||
}
|
||||
|
||||
CustomStorage::CustomStorage(const lt::storage_params ¶ms, lt::file_pool &filePool)
|
||||
: lt::default_storage {params, filePool}
|
||||
: lt::default_storage(params, filePool)
|
||||
, m_savePath {params.path}
|
||||
{
|
||||
}
|
||||
|
|
|
@ -37,17 +37,12 @@
|
|||
#include "addtorrentparams.h"
|
||||
#include "categoryoptions.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
|
||||
enum DeleteOption
|
||||
{
|
||||
DeleteTorrent,
|
||||
DeleteTorrentAndFiles
|
||||
};
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
|
@ -58,6 +53,12 @@ namespace BitTorrent
|
|||
struct CacheStatus;
|
||||
struct SessionStatus;
|
||||
|
||||
enum class TorrentRemoveOption
|
||||
{
|
||||
KeepContent,
|
||||
RemoveContent
|
||||
};
|
||||
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
|
@ -425,7 +426,7 @@ namespace BitTorrent
|
|||
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
|
||||
virtual QStringList excludedFileNames() const = 0;
|
||||
virtual void setExcludedFileNames(const QStringList &newList) = 0;
|
||||
virtual bool isFilenameExcluded(const QString &fileName) const = 0;
|
||||
virtual void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) = 0;
|
||||
virtual QStringList bannedIPs() const = 0;
|
||||
virtual void setBannedIPs(const QStringList &newList) = 0;
|
||||
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
||||
|
@ -434,6 +435,8 @@ namespace BitTorrent
|
|||
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
||||
virtual bool isStartPaused() const = 0;
|
||||
virtual void setStartPaused(bool value) = 0;
|
||||
virtual TorrentContentRemoveOption torrentContentRemoveOption() const = 0;
|
||||
virtual void setTorrentContentRemoveOption(TorrentContentRemoveOption option) = 0;
|
||||
|
||||
virtual bool isRestored() const = 0;
|
||||
|
||||
|
@ -453,7 +456,7 @@ namespace BitTorrent
|
|||
|
||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 0;
|
||||
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
|
||||
virtual bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) = 0;
|
||||
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
#include "nativesessionextension.h"
|
||||
#include "portforwarderimpl.h"
|
||||
#include "resumedatastorage.h"
|
||||
#include "torrentcontentremover.h"
|
||||
#include "torrentdescriptor.h"
|
||||
#include "torrentimpl.h"
|
||||
#include "tracker.h"
|
||||
|
@ -525,6 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
||||
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
||||
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
||||
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::MoveToTrash}
|
||||
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
||||
, m_seedingLimitTimer {new QTimer(this)}
|
||||
, m_resumeDataTimer {new QTimer(this)}
|
||||
|
@ -550,7 +552,14 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||
, this, [this]() { m_recentErroredTorrents.clear(); });
|
||||
|
||||
m_seedingLimitTimer->setInterval(10s);
|
||||
connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
|
||||
connect(m_seedingLimitTimer, &QTimer::timeout, this, [this]
|
||||
{
|
||||
// We shouldn't iterate over `m_torrents` in the loop below
|
||||
// since `deleteTorrent()` modifies it indirectly
|
||||
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
||||
for (TorrentImpl *torrent : torrents)
|
||||
processTorrentShareLimits(torrent);
|
||||
});
|
||||
|
||||
initializeNativeSession();
|
||||
configureComponents();
|
||||
|
@ -586,6 +595,11 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||
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());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_torrentContentRemover, &QObject::deleteLater);
|
||||
connect(m_torrentContentRemover, &TorrentContentRemover::jobFinished, this, &SessionImpl::torrentContentRemovingFinished);
|
||||
|
||||
m_ioThread->start();
|
||||
|
||||
initMetrics();
|
||||
|
@ -604,7 +618,7 @@ SessionImpl::~SessionImpl()
|
|||
{
|
||||
m_nativeSession->pause();
|
||||
|
||||
const qint64 timeout = (m_shutdownTimeout >= 0) ? (m_shutdownTimeout * 1000) : -1;
|
||||
const auto timeout = (m_shutdownTimeout >= 0) ? (static_cast<qint64>(m_shutdownTimeout) * 1000) : -1;
|
||||
const QDeadlineTimer shutdownDeadlineTimer {timeout};
|
||||
|
||||
if (m_torrentsQueueChanged)
|
||||
|
@ -2236,21 +2250,16 @@ void SessionImpl::populateAdditionalTrackers()
|
|||
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
|
||||
}
|
||||
|
||||
void SessionImpl::processShareLimits()
|
||||
void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
|
||||
{
|
||||
if (!torrent->isFinished() || torrent->isForced())
|
||||
return;
|
||||
|
||||
const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
|
||||
{
|
||||
return (limit == useGlobalLimit) ? globalLimit : limit;
|
||||
};
|
||||
|
||||
// We shouldn't iterate over `m_torrents` in the loop below
|
||||
// since `deleteTorrent()` modifies it indirectly
|
||||
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
||||
for (const auto &[torrentID, torrent] : torrents.asKeyValueRange())
|
||||
{
|
||||
if (!torrent->isFinished() || torrent->isForced())
|
||||
continue;
|
||||
|
||||
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
||||
const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
|
||||
const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
|
||||
|
@ -2285,12 +2294,12 @@ void SessionImpl::processShareLimits()
|
|||
if (shareLimitAction == ShareLimitAction::Remove)
|
||||
{
|
||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
|
||||
deleteTorrent(torrentID);
|
||||
removeTorrent(torrent->id(), TorrentRemoveOption::KeepContent);
|
||||
}
|
||||
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
|
||||
{
|
||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
|
||||
deleteTorrent(torrentID, DeleteTorrentAndFiles);
|
||||
removeTorrent(torrent->id(), TorrentRemoveOption::RemoveContent);
|
||||
}
|
||||
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
|
||||
{
|
||||
|
@ -2303,7 +2312,6 @@ void SessionImpl::processShareLimits()
|
|||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
||||
|
@ -2331,6 +2339,19 @@ void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath,
|
|||
}
|
||||
}
|
||||
|
||||
void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage)
|
||||
{
|
||||
if (errorMessage.isEmpty())
|
||||
{
|
||||
LogMsg(tr("Torrent content removed. Torrent: \"%1\"").arg(torrentName));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Failed to remove torrent content. Torrent: \"%1\". Error: \"%2\"")
|
||||
.arg(torrentName, errorMessage), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
|
||||
{
|
||||
return m_torrents.value(id);
|
||||
|
@ -2377,22 +2398,25 @@ void SessionImpl::banIP(const QString &ip)
|
|||
|
||||
// Delete a torrent from the session, given its hash
|
||||
// and from the disk, if the corresponding deleteOption is chosen
|
||||
bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
|
||||
bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption deleteOption)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.take(id);
|
||||
if (!torrent)
|
||||
return false;
|
||||
|
||||
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
|
||||
const TorrentID torrentID = torrent->id();
|
||||
const QString torrentName = torrent->name();
|
||||
|
||||
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrentID.toString()));
|
||||
emit torrentAboutToBeRemoved(torrent);
|
||||
|
||||
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
||||
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
||||
|
||||
// Remove it from session
|
||||
if (deleteOption == DeleteTorrent)
|
||||
if (deleteOption == TorrentRemoveOption::KeepContent)
|
||||
{
|
||||
m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
|
||||
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), {}, deleteOption};
|
||||
|
||||
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
||||
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
||||
|
@ -2414,7 +2438,7 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
|||
}
|
||||
else
|
||||
{
|
||||
m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
|
||||
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), torrent->actualFilePaths(), deleteOption};
|
||||
|
||||
if (m_moveStorageQueue.size() > 1)
|
||||
{
|
||||
|
@ -2429,12 +2453,13 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
|||
m_moveStorageQueue.erase(iter);
|
||||
}
|
||||
|
||||
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
|
||||
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_partfile);
|
||||
}
|
||||
|
||||
// Remove it from torrent resume directory
|
||||
m_resumeDataStorage->remove(torrent->id());
|
||||
m_resumeDataStorage->remove(torrentID);
|
||||
|
||||
LogMsg(tr("Torrent removed. Torrent: \"%1\"").arg(torrentName));
|
||||
delete torrent;
|
||||
return true;
|
||||
}
|
||||
|
@ -2462,7 +2487,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
|||
}
|
||||
#endif
|
||||
|
||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
|
||||
m_nativeSession->remove_torrent(nativeHandle);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2769,26 +2794,22 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||
Q_ASSERT(p.file_priorities.empty());
|
||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||
|
||||
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
||||
|
||||
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
||||
{
|
||||
// Check file name blacklist when priorities are not explicitly set
|
||||
applyFilenameFilter(filePaths, filePriorities);
|
||||
}
|
||||
|
||||
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||
|
||||
if (addTorrentParams.filePriorities.isEmpty())
|
||||
if (!filePriorities.isEmpty())
|
||||
{
|
||||
if (isExcludedFileNamesEnabled())
|
||||
{
|
||||
// Check file name blacklist when priorities are not explicitly set
|
||||
for (int i = 0; i < filePaths.size(); ++i)
|
||||
{
|
||||
if (isFilenameExcluded(filePaths.at(i).filename()))
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
|
||||
for (int i = 0; i < filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(filePriorities[i]);
|
||||
}
|
||||
|
||||
Q_ASSERT(p.ti);
|
||||
|
@ -3874,21 +3895,41 @@ void SessionImpl::populateExcludedFileNamesRegExpList()
|
|||
|
||||
for (const QString &str : excludedNames)
|
||||
{
|
||||
const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
|
||||
const QString pattern = QRegularExpression::wildcardToRegularExpression(str);
|
||||
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
|
||||
m_excludedFileNamesRegExpList.append(re);
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionImpl::isFilenameExcluded(const QString &fileName) const
|
||||
void SessionImpl::applyFilenameFilter(const PathList &files, QList<DownloadPriority> &priorities)
|
||||
{
|
||||
if (!isExcludedFileNamesEnabled())
|
||||
return false;
|
||||
return;
|
||||
|
||||
return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
|
||||
const auto isFilenameExcluded = [patterns = m_excludedFileNamesRegExpList](const Path &fileName)
|
||||
{
|
||||
return re.match(fileName).hasMatch();
|
||||
return std::any_of(patterns.begin(), patterns.end(), [&fileName](const QRegularExpression &re)
|
||||
{
|
||||
Path path = fileName;
|
||||
while (!re.match(path.filename()).hasMatch())
|
||||
{
|
||||
path = path.parentPath();
|
||||
if (path.isEmpty())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
priorities.resize(files.count(), DownloadPriority::Normal);
|
||||
for (int i = 0; i < priorities.size(); ++i)
|
||||
{
|
||||
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
||||
continue;
|
||||
|
||||
if (isFilenameExcluded(files.at(i)))
|
||||
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::setBannedIPs(const QStringList &newList)
|
||||
|
@ -3957,6 +3998,16 @@ void SessionImpl::setStartPaused(const bool value)
|
|||
m_startPaused = value;
|
||||
}
|
||||
|
||||
TorrentContentRemoveOption SessionImpl::torrentContentRemoveOption() const
|
||||
{
|
||||
return m_torrentContentRemoveOption;
|
||||
}
|
||||
|
||||
void SessionImpl::setTorrentContentRemoveOption(const TorrentContentRemoveOption option)
|
||||
{
|
||||
m_torrentContentRemoveOption = option;
|
||||
}
|
||||
|
||||
QStringList SessionImpl::bannedIPs() const
|
||||
{
|
||||
return m_bannedIPs;
|
||||
|
@ -5002,18 +5053,7 @@ void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
|
|||
|
||||
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
||||
{
|
||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||
exportTorrentFile(torrent, exportPath);
|
||||
|
||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||
{
|
||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||
});
|
||||
if (!hasUnfinishedTorrents)
|
||||
emit allTorrentsFinished();
|
||||
m_pendingFinishedTorrents.append(torrent);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
||||
|
@ -5141,7 +5181,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
|||
// 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()];
|
||||
if (removingTorrentData.deleteOption == DeleteTorrent)
|
||||
if (removingTorrentData.removeOption == TorrentRemoveOption::KeepContent)
|
||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
|
||||
}
|
||||
}
|
||||
|
@ -5660,74 +5700,32 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
|
|||
return torrent;
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert)
|
||||
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*alert*/)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
if (removingTorrentDataIter != m_removingTorrents.end())
|
||||
{
|
||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
||||
{
|
||||
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
}
|
||||
}
|
||||
// We cannot consider `torrent_removed_alert` as a starting point for removing content,
|
||||
// because it has an inconsistent posting time between different versions of libtorrent,
|
||||
// so files may still be in use in some cases.
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
|
||||
// torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
|
||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
||||
return;
|
||||
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
||||
LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
handleRemovedTorrent(torrentID);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
||||
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
|
||||
if (alert->error)
|
||||
{
|
||||
// libtorrent won't delete the directory if it contains files not listed in the torrent,
|
||||
// so we remove the directory ourselves
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
||||
|
||||
LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
|
||||
.arg(removingTorrentDataIter->name, QString::fromLocal8Bit(alert->error.message().c_str()))
|
||||
, Log::WARNING);
|
||||
}
|
||||
else // torrent without metadata, hence no files on disk
|
||||
{
|
||||
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
||||
}
|
||||
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
|
||||
handleRemovedTorrent(torrentID, errorMessage);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
|
||||
|
@ -6079,6 +6077,29 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
|
|||
if (!updatedTorrents.isEmpty())
|
||||
emit torrentsUpdated(updatedTorrents);
|
||||
|
||||
if (!m_pendingFinishedTorrents.isEmpty())
|
||||
{
|
||||
for (TorrentImpl *torrent : m_pendingFinishedTorrents)
|
||||
{
|
||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||
exportTorrentFile(torrent, exportPath);
|
||||
|
||||
processTorrentShareLimits(torrent);
|
||||
}
|
||||
|
||||
m_pendingFinishedTorrents.clear();
|
||||
|
||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||
{
|
||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||
});
|
||||
if (!hasUnfinishedTorrents)
|
||||
emit allTorrentsFinished();
|
||||
}
|
||||
|
||||
if (m_needSaveTorrentsQueue)
|
||||
saveTorrentsQueue();
|
||||
|
||||
|
@ -6140,7 +6161,7 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
|
|||
if (torrent2)
|
||||
{
|
||||
if (torrent1)
|
||||
deleteTorrent(torrentIDv1);
|
||||
removeTorrent(torrentIDv1);
|
||||
else
|
||||
cancelDownloadMetadata(torrentIDv1);
|
||||
|
||||
|
@ -6249,3 +6270,29 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, Q
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
|
||||
{
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
|
||||
if (!partfileRemoveError.isEmpty())
|
||||
{
|
||||
LogMsg(tr("Failed to remove partfile. Torrent: \"%1\". Reason: \"%2\".")
|
||||
.arg(removingTorrentDataIter->name, partfileRemoveError)
|
||||
, Log::WARNING);
|
||||
}
|
||||
|
||||
if ((removingTorrentDataIter->removeOption == TorrentRemoveOption::RemoveContent)
|
||||
&& !removingTorrentDataIter->contentStoragePath.isEmpty())
|
||||
{
|
||||
QMetaObject::invokeMethod(m_torrentContentRemover, [this, jobData = *removingTorrentDataIter]
|
||||
{
|
||||
m_torrentContentRemover->performJob(jobData.name, jobData.contentStoragePath
|
||||
, jobData.fileNames, m_torrentContentRemoveOption);
|
||||
});
|
||||
}
|
||||
|
||||
m_removingTorrents.erase(removingTorrentDataIter);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ namespace BitTorrent
|
|||
class InfoHash;
|
||||
class ResumeDataStorage;
|
||||
class Torrent;
|
||||
class TorrentContentRemover;
|
||||
class TorrentDescriptor;
|
||||
class TorrentImpl;
|
||||
class Tracker;
|
||||
|
@ -402,7 +403,7 @@ namespace BitTorrent
|
|||
void setExcludedFileNamesEnabled(bool enabled) override;
|
||||
QStringList excludedFileNames() const override;
|
||||
void setExcludedFileNames(const QStringList &excludedFileNames) override;
|
||||
bool isFilenameExcluded(const QString &fileName) const override;
|
||||
void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) override;
|
||||
QStringList bannedIPs() const override;
|
||||
void setBannedIPs(const QStringList &newList) override;
|
||||
ResumeDataStorageType resumeDataStorageType() const override;
|
||||
|
@ -411,6 +412,8 @@ namespace BitTorrent
|
|||
void setMergeTrackersEnabled(bool enabled) override;
|
||||
bool isStartPaused() const override;
|
||||
void setStartPaused(bool value) override;
|
||||
TorrentContentRemoveOption torrentContentRemoveOption() const override;
|
||||
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
|
||||
|
||||
bool isRestored() const override;
|
||||
|
||||
|
@ -430,7 +433,7 @@ namespace BitTorrent
|
|||
|
||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) override;
|
||||
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
|
||||
bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) override;
|
||||
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||
|
||||
|
@ -487,11 +490,11 @@ namespace BitTorrent
|
|||
void configureDeferred();
|
||||
void readAlerts();
|
||||
void enqueueRefresh();
|
||||
void processShareLimits();
|
||||
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:
|
||||
struct ResumeSessionContext;
|
||||
|
@ -507,8 +510,9 @@ namespace BitTorrent
|
|||
struct RemovingTorrentData
|
||||
{
|
||||
QString name;
|
||||
Path pathToRemove;
|
||||
DeleteOption deleteOption {};
|
||||
Path contentStoragePath;
|
||||
PathList fileNames;
|
||||
TorrentRemoveOption removeOption {};
|
||||
};
|
||||
|
||||
explicit SessionImpl(QObject *parent = nullptr);
|
||||
|
@ -536,6 +540,7 @@ namespace BitTorrent
|
|||
void enableIPFilter();
|
||||
void disableIPFilter();
|
||||
void processTrackerStatuses();
|
||||
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||
void populateExcludedFileNamesRegExpList();
|
||||
void prepareStartup();
|
||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
||||
|
@ -599,13 +604,7 @@ namespace BitTorrent
|
|||
|
||||
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
||||
|
||||
// BitTorrent
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
|
||||
|
||||
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
||||
CachedSettingValue<bool> m_isDHTEnabled;
|
||||
|
@ -731,8 +730,16 @@ namespace BitTorrent
|
|||
CachedSettingValue<int> m_I2POutboundQuantity;
|
||||
CachedSettingValue<int> m_I2PInboundLength;
|
||||
CachedSettingValue<int> m_I2POutboundLength;
|
||||
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
|
||||
SettingValue<bool> m_startPaused;
|
||||
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
|
||||
bool m_isRestored = false;
|
||||
bool m_isPaused = isStartPaused();
|
||||
|
||||
|
@ -766,6 +773,7 @@ namespace BitTorrent
|
|||
QThreadPool *m_asyncWorker = nullptr;
|
||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
|
@ -809,6 +817,8 @@ namespace BitTorrent
|
|||
QTimer *m_wakeupCheckTimer = nullptr;
|
||||
QDateTime m_wakeupCheckTimestamp;
|
||||
|
||||
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||
|
||||
friend void Session::initInstance();
|
||||
friend void Session::freeInstance();
|
||||
friend Session *Session::instance();
|
||||
|
|
|
@ -228,6 +228,7 @@ namespace BitTorrent
|
|||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||
|
||||
virtual PathList filePaths() const = 0;
|
||||
virtual PathList actualFilePaths() const = 0;
|
||||
|
||||
virtual TorrentInfo info() const = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
|
|
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
inline namespace TorrentContentRemoveOptionNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class TorrentContentRemoveOption
|
||||
{
|
||||
Delete,
|
||||
MoveToTrash
|
||||
};
|
||||
|
||||
Q_ENUM_NS(TorrentContentRemoveOption)
|
||||
}
|
||||
}
|
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "torrentcontentremover.h"
|
||||
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
void BitTorrent::TorrentContentRemover::performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, const TorrentContentRemoveOption option)
|
||||
{
|
||||
QString errorMessage;
|
||||
|
||||
if (!fileNames.isEmpty())
|
||||
{
|
||||
const auto removeFileFn = [&option](const Path &filePath)
|
||||
{
|
||||
return ((option == TorrentContentRemoveOption::MoveToTrash)
|
||||
? Utils::Fs::moveFileToTrash : Utils::Fs::removeFile)(filePath);
|
||||
};
|
||||
|
||||
for (const Path &fileName : fileNames)
|
||||
{
|
||||
if (const auto result = removeFileFn(basePath / fileName)
|
||||
; !result && errorMessage.isEmpty())
|
||||
{
|
||||
errorMessage = result.error();
|
||||
}
|
||||
}
|
||||
|
||||
const Path rootPath = Path::findRootFolder(fileNames);
|
||||
if (!rootPath.isEmpty())
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(basePath / rootPath);
|
||||
}
|
||||
|
||||
emit jobFinished(torrentName, errorMessage);
|
||||
}
|
53
src/base/bittorrent/torrentcontentremover.h
Normal file
53
src/base/bittorrent/torrentcontentremover.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentRemover final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TorrentContentRemover)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
public slots:
|
||||
void performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, TorrentContentRemoveOption option);
|
||||
|
||||
signals:
|
||||
void jobFinished(const QString &torrentName, const QString &errorMessage);
|
||||
};
|
||||
}
|
|
@ -77,6 +77,10 @@
|
|||
#include "base/utils/os.h"
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
#include "customstorage.h"
|
||||
#endif
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
namespace
|
||||
|
@ -982,6 +986,21 @@ PathList TorrentImpl::filePaths() const
|
|||
return m_filePaths;
|
||||
}
|
||||
|
||||
PathList TorrentImpl::actualFilePaths() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
PathList paths;
|
||||
paths.reserve(filesCount());
|
||||
|
||||
const lt::file_storage files = nativeTorrentInfo()->files();
|
||||
for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
|
||||
paths.emplaceBack(files.file_path(nativeIndex));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
||||
{
|
||||
return m_filePriorities;
|
||||
|
@ -1447,11 +1466,13 @@ QBitArray TorrentImpl::pieces() const
|
|||
|
||||
QBitArray TorrentImpl::downloadingPieces() const
|
||||
{
|
||||
QBitArray result(piecesCount());
|
||||
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));
|
||||
|
||||
|
@ -1791,12 +1812,13 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
|||
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
||||
m_filePaths.append(filePath);
|
||||
|
||||
lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
|
||||
if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
|
||||
nativePriority = lt::dont_download;
|
||||
const auto priority = LT::fromNative(nativePriority);
|
||||
m_filePriorities.append(priority);
|
||||
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||
}
|
||||
|
||||
m_session->applyFilenameFilter(fileNames, m_filePriorities);
|
||||
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||
|
||||
p.save_path = savePath.toString().toStdString();
|
||||
p.ti = metadata;
|
||||
|
||||
|
@ -1859,6 +1881,9 @@ void TorrentImpl::reload()
|
|||
|
||||
auto *const extensionData = new ExtensionData;
|
||||
p.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
|
||||
m_nativeStatus = extensionData->status;
|
||||
|
|
|
@ -153,6 +153,7 @@ namespace BitTorrent
|
|||
Path actualFilePath(int index) const override;
|
||||
qlonglong fileSize(int index) const override;
|
||||
PathList filePaths() const override;
|
||||
PathList actualFilePaths() const override;
|
||||
QVector<DownloadPriority> filePriorities() const override;
|
||||
|
||||
TorrentInfo info() const override;
|
||||
|
|
|
@ -44,6 +44,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
|||
, m_requestHandler(requestHandler)
|
||||
{
|
||||
m_socket->setParent(this);
|
||||
connect(m_socket, &QAbstractSocket::disconnected, this, &Connection::closed);
|
||||
|
||||
// reserve common size for requests, don't use the max allowed size which is too big for
|
||||
// memory constrained platforms
|
||||
|
@ -62,11 +63,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
|||
});
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
m_socket->close();
|
||||
}
|
||||
|
||||
void Connection::read()
|
||||
{
|
||||
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
||||
|
@ -182,11 +178,6 @@ bool Connection::hasExpired(const qint64 timeout) const
|
|||
&& m_idleTimer.hasExpired(timeout);
|
||||
}
|
||||
|
||||
bool Connection::isClosed() const
|
||||
{
|
||||
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
||||
}
|
||||
|
||||
bool Connection::acceptsGzipEncoding(QString codings)
|
||||
{
|
||||
// [rfc7231] 5.3.4. Accept-Encoding
|
||||
|
|
|
@ -47,10 +47,11 @@ namespace Http
|
|||
|
||||
public:
|
||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||
~Connection();
|
||||
|
||||
bool hasExpired(qint64 timeout) const;
|
||||
bool isClosed() const;
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
||||
private:
|
||||
static bool acceptsGzipEncoding(QString codings);
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
|
||||
#include <QtLogging>
|
||||
#include <QNetworkProxy>
|
||||
#include <QSslCipher>
|
||||
#include <QSslConfiguration>
|
||||
|
@ -40,7 +43,6 @@
|
|||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/global.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/sslkey.h"
|
||||
|
@ -113,32 +115,38 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
|||
|
||||
void Server::incomingConnection(const qintptr socketDescriptor)
|
||||
{
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
||||
|
||||
QTcpSocket *serverSocket = nullptr;
|
||||
if (m_https)
|
||||
serverSocket = new QSslSocket(this);
|
||||
else
|
||||
serverSocket = new QTcpSocket(this);
|
||||
|
||||
std::unique_ptr<QTcpSocket> serverSocket = m_https ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
|
||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
||||
return;
|
||||
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT)
|
||||
{
|
||||
delete serverSocket;
|
||||
qWarning("Too many connections. Exceeded CONNECTIONS_LIMIT (%d). Connection closed.", CONNECTIONS_LIMIT);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m_https)
|
||||
{
|
||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
||||
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
|
||||
sslSocket->setProtocol(QSsl::SecureProtocols);
|
||||
sslSocket->setPrivateKey(m_key);
|
||||
sslSocket->setLocalCertificateChain(m_certificates);
|
||||
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
sslSocket->startServerEncryption();
|
||||
}
|
||||
|
||||
auto *c = new Connection(serverSocket, m_requestHandler, this);
|
||||
m_connections.insert(c);
|
||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
||||
auto *connection = new Connection(serverSocket.release(), m_requestHandler, this);
|
||||
m_connections.insert(connection);
|
||||
connect(connection, &Connection::closed, this, [this, connection] { removeConnection(connection); });
|
||||
}
|
||||
catch (const std::bad_alloc &exception)
|
||||
{
|
||||
// drop the connection instead of throwing exception and crash
|
||||
qWarning("Failed to allocate memory for HTTP connection. Connection closed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::removeConnection(Connection *connection)
|
||||
|
|
|
@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
|
|||
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
||||
}
|
||||
|
||||
bool Preferences::deleteTorrentFilesAsDefault() const
|
||||
bool Preferences::removeTorrentContent() const
|
||||
{
|
||||
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setDeleteTorrentFilesAsDefault(const bool del)
|
||||
void Preferences::setRemoveTorrentContent(const bool remove)
|
||||
{
|
||||
if (del == deleteTorrentFilesAsDefault())
|
||||
if (remove == removeTorrentContent())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
|
||||
}
|
||||
|
||||
bool Preferences::confirmOnExit() const
|
||||
|
|
|
@ -105,8 +105,8 @@ public:
|
|||
void setUseCustomUITheme(bool use);
|
||||
Path customUIThemePath() const;
|
||||
void setCustomUIThemePath(const Path &path);
|
||||
bool deleteTorrentFilesAsDefault() const;
|
||||
void setDeleteTorrentFilesAsDefault(bool del);
|
||||
bool removeTorrentContent() const;
|
||||
void setRemoveTorrentContent(bool remove);
|
||||
bool confirmOnExit() const;
|
||||
void setConfirmOnExit(bool confirm);
|
||||
bool speedInTitleBar() const;
|
||||
|
|
|
@ -52,19 +52,21 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
|||
using BitTorrent::Torrent;
|
||||
|
||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag)
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_type {type}
|
||||
, m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
{
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag)
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
{
|
||||
setTypeByName(filter);
|
||||
}
|
||||
|
@ -147,11 +149,22 @@ bool TorrentFilter::setTag(const std::optional<Tag> &tag)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::setPrivate(const std::optional<bool> isPrivate)
|
||||
{
|
||||
if (m_private != isPrivate)
|
||||
{
|
||||
m_private = isPrivate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
{
|
||||
if (!torrent) return false;
|
||||
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent));
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent) && matchPrivate(torrent));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
|
@ -224,3 +237,11 @@ bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
|||
|
||||
return torrent->hasTag(*m_tag);
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchPrivate(const BitTorrent::Torrent *const torrent) const
|
||||
{
|
||||
if (!m_private)
|
||||
return true;
|
||||
|
||||
return m_private == torrent->isPrivate();
|
||||
}
|
||||
|
|
|
@ -87,16 +87,24 @@ public:
|
|||
|
||||
TorrentFilter() = default;
|
||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||
TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tag = AnyTag);
|
||||
TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tags = AnyTag);
|
||||
TorrentFilter(Type type
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tag = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
TorrentFilter(const QString &filter
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tags = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
|
||||
|
||||
bool setType(Type type);
|
||||
bool setTypeByName(const QString &filter);
|
||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||
bool setCategory(const std::optional<QString> &category);
|
||||
bool setTag(const std::optional<Tag> &tag);
|
||||
bool setPrivate(std::optional<bool> isPrivate);
|
||||
|
||||
bool match(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
|
@ -105,9 +113,11 @@ private:
|
|||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
Type m_type {All};
|
||||
std::optional<QString> m_category;
|
||||
std::optional<Tag> m_tag;
|
||||
std::optional<TorrentIDSet> m_idSet;
|
||||
std::optional<bool> m_private;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -29,8 +29,6 @@
|
|||
|
||||
#include "fs.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -52,6 +50,7 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
@ -311,20 +310,42 @@ bool Utils::Fs::renameFile(const Path &from, const Path &to)
|
|||
*
|
||||
* This function will try to fix the file permissions before removing it.
|
||||
*/
|
||||
bool Utils::Fs::removeFile(const Path &path)
|
||||
nonstd::expected<void, QString> Utils::Fs::removeFile(const Path &path)
|
||||
{
|
||||
if (QFile::remove(path.data()))
|
||||
return true;
|
||||
|
||||
QFile file {path.data()};
|
||||
if (file.remove())
|
||||
return {};
|
||||
|
||||
if (!file.exists())
|
||||
return true;
|
||||
return {};
|
||||
|
||||
// Make sure we have read/write permissions
|
||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
return file.remove();
|
||||
if (file.remove())
|
||||
return {};
|
||||
|
||||
return nonstd::make_unexpected(file.errorString());
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> Utils::Fs::moveFileToTrash(const Path &path)
|
||||
{
|
||||
QFile file {path.data()};
|
||||
if (file.moveToTrash())
|
||||
return {};
|
||||
|
||||
if (!file.exists())
|
||||
return {};
|
||||
|
||||
// Make sure we have read/write permissions
|
||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
if (file.moveToTrash())
|
||||
return {};
|
||||
|
||||
const QString errorMessage = file.errorString();
|
||||
return nonstd::make_unexpected(!errorMessage.isEmpty() ? errorMessage : QCoreApplication::translate("fs", "Unknown error"));
|
||||
}
|
||||
|
||||
|
||||
bool Utils::Fs::isReadable(const Path &path)
|
||||
{
|
||||
return QFileInfo(path.data()).isReadable();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include <QString>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/global.h"
|
||||
#include "base/pathfwd.h"
|
||||
|
||||
|
@ -60,7 +61,8 @@ namespace Utils::Fs
|
|||
|
||||
bool copyFile(const Path &from, const Path &to);
|
||||
bool renameFile(const Path &from, const Path &to);
|
||||
bool removeFile(const Path &path);
|
||||
nonstd::expected<void, QString> removeFile(const Path &path);
|
||||
nonstd::expected<void, QString> moveFileToTrash(const Path &path);
|
||||
bool mkdir(const Path &dirPath);
|
||||
bool mkpath(const Path &dirPath);
|
||||
bool rmdir(const Path &dirPath);
|
||||
|
|
|
@ -52,6 +52,8 @@ add_library(qbt_gui STATIC
|
|||
desktopintegration.h
|
||||
downloadfromurldialog.h
|
||||
executionlogwidget.h
|
||||
filterpatternformat.h
|
||||
filterpatternformatmenu.h
|
||||
flowlayout.h
|
||||
fspathedit.h
|
||||
fspathedit_p.h
|
||||
|
@ -151,6 +153,7 @@ add_library(qbt_gui STATIC
|
|||
desktopintegration.cpp
|
||||
downloadfromurldialog.cpp
|
||||
executionlogwidget.cpp
|
||||
filterpatternformatmenu.cpp
|
||||
flowlayout.cpp
|
||||
fspathedit.cpp
|
||||
fspathedit_p.cpp
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -64,6 +64,7 @@
|
|||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "filterpatternformatmenu.h"
|
||||
#include "lineedit.h"
|
||||
#include "torrenttagsdialog.h"
|
||||
|
||||
|
@ -181,6 +182,11 @@ public:
|
|||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
|
||||
}
|
||||
|
||||
PathList filePaths() const
|
||||
{
|
||||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePaths() : m_filePaths);
|
||||
}
|
||||
|
||||
void renameFile(const int index, const Path &newFilePath) override
|
||||
{
|
||||
Q_ASSERT((index >= 0) && (index < filesCount()));
|
||||
|
@ -290,6 +296,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
|||
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
||||
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
||||
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
||||
, m_storeFilterPatternFormat {u"GUI/" SETTINGS_KEY(u"FilterPatternFormat"_s)}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
|
@ -316,6 +323,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
|||
// Torrent content filtering
|
||||
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
||||
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
m_filterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_filterLine, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::showContentFilterContextMenu);
|
||||
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
|
||||
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
||||
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
||||
|
@ -360,7 +369,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
|||
});
|
||||
dlg->open();
|
||||
});
|
||||
connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
|
||||
connect(m_filterLine, &LineEdit::textChanged, this, &AddNewTorrentDialog::setContentFilterPattern);
|
||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||
connect(Preferences::instance(), &Preferences::changed, []
|
||||
|
@ -691,6 +700,28 @@ void AddNewTorrentDialog::saveTorrentFile()
|
|||
}
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::showContentFilterContextMenu()
|
||||
{
|
||||
QMenu *menu = m_filterLine->createStandardContextMenu();
|
||||
|
||||
auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
|
||||
connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
|
||||
{
|
||||
m_storeFilterPatternFormat = format;
|
||||
setContentFilterPattern();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addMenu(formatMenu);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::setContentFilterPattern()
|
||||
{
|
||||
m_ui->contentTreeView->setFilterPattern(m_filterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::populateSavePaths()
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
|
@ -886,15 +917,7 @@ void AddNewTorrentDialog::setupTreeview()
|
|||
{
|
||||
// Check file name blacklist for torrents that are manually added
|
||||
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
||||
for (int i = 0; i < priorities.size(); ++i)
|
||||
{
|
||||
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
||||
continue;
|
||||
|
||||
if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
|
||||
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
||||
}
|
||||
|
||||
BitTorrent::Session::instance()->applyFilenameFilter(m_contentAdaptor->filePaths(), priorities);
|
||||
m_contentAdaptor->prioritizeFiles(priorities);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "filterpatternformat.h"
|
||||
|
||||
class LineEdit;
|
||||
|
||||
|
@ -92,6 +93,8 @@ private:
|
|||
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
||||
void setupTreeview();
|
||||
void saveTorrentFile();
|
||||
void showContentFilterContextMenu();
|
||||
void setContentFilterPattern();
|
||||
|
||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
||||
|
@ -107,4 +110,5 @@ private:
|
|||
SettingValue<bool> m_storeRememberLastSavePath;
|
||||
SettingValue<QByteArray> m_storeTreeHeaderState;
|
||||
SettingValue<QByteArray> m_storeSplitterState;
|
||||
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||
};
|
||||
|
|
|
@ -63,6 +63,7 @@ namespace
|
|||
// qBittorrent section
|
||||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
TORRENT_CONTENT_REMOVE_OPTION,
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
MEMORY_WORKING_SET_LIMIT,
|
||||
#endif
|
||||
|
@ -364,6 +365,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
|||
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
||||
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
||||
#endif
|
||||
|
||||
session->setTorrentContentRemoveOption(m_comboBoxTorrentContentRemoveOption.currentData().value<BitTorrent::TorrentContentRemoveOption>());
|
||||
}
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
|
@ -472,6 +475,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
||||
|
||||
m_comboBoxTorrentContentRemoveOption.addItem(tr("Delete files permanently"), QVariant::fromValue(BitTorrent::TorrentContentRemoveOption::Delete));
|
||||
m_comboBoxTorrentContentRemoveOption.addItem(tr("Move files to trash (if possible)"), QVariant::fromValue(BitTorrent::TorrentContentRemoveOption::MoveToTrash));
|
||||
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)
|
||||
// Physical memory (RAM) usage limit
|
||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||
|
|
|
@ -81,7 +81,7 @@ private:
|
|||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage;
|
||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage, m_comboBoxTorrentContentRemoveOption;
|
||||
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -30,6 +31,7 @@
|
|||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "uithememanager.h"
|
||||
|
@ -53,8 +55,8 @@ DeletionConfirmationDialog::DeletionConfirmationDialog(QWidget *parent, const in
|
|||
m_ui->rememberBtn->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
|
||||
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
||||
|
||||
m_ui->checkPermDelete->setChecked(defaultDeleteFiles || Preferences::instance()->deleteTorrentFilesAsDefault());
|
||||
connect(m_ui->checkPermDelete, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||
m_ui->checkRemoveContent->setChecked(defaultDeleteFiles || Preferences::instance()->removeTorrentContent());
|
||||
connect(m_ui->checkRemoveContent, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
||||
|
||||
|
@ -67,18 +69,18 @@ DeletionConfirmationDialog::~DeletionConfirmationDialog()
|
|||
delete m_ui;
|
||||
}
|
||||
|
||||
bool DeletionConfirmationDialog::isDeleteFileSelected() const
|
||||
bool DeletionConfirmationDialog::isRemoveContentSelected() const
|
||||
{
|
||||
return m_ui->checkPermDelete->isChecked();
|
||||
return m_ui->checkRemoveContent->isChecked();
|
||||
}
|
||||
|
||||
void DeletionConfirmationDialog::updateRememberButtonState()
|
||||
{
|
||||
m_ui->rememberBtn->setEnabled(m_ui->checkPermDelete->isChecked() != Preferences::instance()->deleteTorrentFilesAsDefault());
|
||||
m_ui->rememberBtn->setEnabled(m_ui->checkRemoveContent->isChecked() != Preferences::instance()->removeTorrentContent());
|
||||
}
|
||||
|
||||
void DeletionConfirmationDialog::on_rememberBtn_clicked()
|
||||
{
|
||||
Preferences::instance()->setDeleteTorrentFilesAsDefault(m_ui->checkPermDelete->isChecked());
|
||||
Preferences::instance()->setRemoveTorrentContent(m_ui->checkRemoveContent->isChecked());
|
||||
m_ui->rememberBtn->setEnabled(false);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -37,16 +38,16 @@ namespace Ui
|
|||
class DeletionConfirmationDialog;
|
||||
}
|
||||
|
||||
class DeletionConfirmationDialog : public QDialog
|
||||
class DeletionConfirmationDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
||||
|
||||
public:
|
||||
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
||||
~DeletionConfirmationDialog();
|
||||
~DeletionConfirmationDialog() override;
|
||||
|
||||
bool isDeleteFileSelected() const;
|
||||
bool isRemoveContentSelected() const;
|
||||
|
||||
private slots:
|
||||
void updateRememberButtonState();
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkPermDelete">
|
||||
<widget class="QCheckBox" name="checkRemoveContent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -88,7 +88,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Also permanently delete the files</string>
|
||||
<string>Also remove the content files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
48
src/gui/filterpatternformat.h
Normal file
48
src/gui/filterpatternformat.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
inline namespace FilterPatternFormatNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class FilterPatternFormat
|
||||
{
|
||||
PlainText,
|
||||
Wildcards,
|
||||
Regex
|
||||
};
|
||||
|
||||
Q_ENUM_NS(FilterPatternFormat)
|
||||
}
|
82
src/gui/filterpatternformatmenu.cpp
Normal file
82
src/gui/filterpatternformatmenu.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "filterpatternformatmenu.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
|
||||
FilterPatternFormatMenu::FilterPatternFormatMenu(const FilterPatternFormat format, QWidget *parent)
|
||||
: QMenu(parent)
|
||||
{
|
||||
setTitle(tr("Pattern Format"));
|
||||
|
||||
auto *patternFormatGroup = new QActionGroup(this);
|
||||
patternFormatGroup->setExclusive(true);
|
||||
|
||||
QAction *plainTextAction = addAction(tr("Plain text"));
|
||||
plainTextAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(plainTextAction);
|
||||
|
||||
QAction *wildcardsAction = addAction(tr("Wildcards"));
|
||||
wildcardsAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(wildcardsAction);
|
||||
|
||||
QAction *regexAction = addAction(tr("Regular expression"));
|
||||
regexAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(regexAction);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case FilterPatternFormat::Wildcards:
|
||||
default:
|
||||
wildcardsAction->setChecked(true);
|
||||
break;
|
||||
case FilterPatternFormat::PlainText:
|
||||
plainTextAction->setChecked(true);
|
||||
break;
|
||||
case FilterPatternFormat::Regex:
|
||||
regexAction->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
connect(plainTextAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::PlainText);
|
||||
});
|
||||
connect(wildcardsAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::Wildcards);
|
||||
});
|
||||
connect(regexAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::Regex);
|
||||
});
|
||||
}
|
45
src/gui/filterpatternformatmenu.h
Normal file
45
src/gui/filterpatternformatmenu.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
#include "filterpatternformat.h"
|
||||
|
||||
class FilterPatternFormatMenu final : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(FilterPatternFormatMenu)
|
||||
|
||||
public:
|
||||
explicit FilterPatternFormatMenu(FilterPatternFormat format, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void patternFormatChanged(FilterPatternFormat format);
|
||||
};
|
|
@ -411,7 +411,7 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
|||
return;
|
||||
|
||||
// Remove I2P peers since they will be completely reloaded.
|
||||
for (QStandardItem *item : asConst(m_I2PPeerItems))
|
||||
for (const QStandardItem *item : asConst(m_I2PPeerItems))
|
||||
m_listModel->removeRow(item->row());
|
||||
m_I2PPeerItems.clear();
|
||||
|
||||
|
@ -466,10 +466,14 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
|||
{
|
||||
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
||||
|
||||
QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
|
||||
items.remove(item);
|
||||
if (items.isEmpty())
|
||||
m_itemsByIP.remove(peerEndpoint.address.ip);
|
||||
const auto items = m_itemsByIP.find(peerEndpoint.address.ip);
|
||||
Q_ASSERT(items != m_itemsByIP.end());
|
||||
if (items == m_itemsByIP.end()) [[unlikely]]
|
||||
continue;
|
||||
|
||||
items->remove(item);
|
||||
if (items->isEmpty())
|
||||
m_itemsByIP.erase(items);
|
||||
|
||||
m_listModel->removeRow(item->row());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -52,6 +52,7 @@
|
|||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "gui/autoexpandabledialog.h"
|
||||
#include "gui/filterpatternformatmenu.h"
|
||||
#include "gui/lineedit.h"
|
||||
#include "gui/trackerlist/trackerlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
@ -66,6 +67,7 @@
|
|||
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui {new Ui::PropertiesWidget}
|
||||
, m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
#ifndef Q_OS_MACOS
|
||||
|
@ -78,7 +80,9 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
|
|||
m_contentFilterLine = new LineEdit(this);
|
||||
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
||||
m_contentFilterLine->setFixedWidth(300);
|
||||
connect(m_contentFilterLine, &LineEdit::textChanged, m_ui->filesList, &TorrentContentWidget::setFilterPattern);
|
||||
m_contentFilterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_contentFilterLine, &QWidget::customContextMenuRequested, this, &PropertiesWidget::showContentFilterContextMenu);
|
||||
connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::setContentFilterPattern);
|
||||
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
||||
|
||||
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
||||
|
@ -206,6 +210,7 @@ void PropertiesWidget::clear()
|
|||
m_ui->labelSavePathVal->clear();
|
||||
m_ui->labelCreatedOnVal->clear();
|
||||
m_ui->labelTotalPiecesVal->clear();
|
||||
m_ui->labelPrivateVal->clear();
|
||||
m_ui->labelInfohash1Val->clear();
|
||||
m_ui->labelInfohash2Val->clear();
|
||||
m_ui->labelCommentVal->clear();
|
||||
|
@ -274,6 +279,28 @@ void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
|
|||
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
|
||||
}
|
||||
|
||||
void PropertiesWidget::showContentFilterContextMenu()
|
||||
{
|
||||
QMenu *menu = m_contentFilterLine->createStandardContextMenu();
|
||||
|
||||
auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
|
||||
connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
|
||||
{
|
||||
m_storeFilterPatternFormat = format;
|
||||
setContentFilterPattern();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addMenu(formatMenu);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void PropertiesWidget::setContentFilterPattern()
|
||||
{
|
||||
m_ui->filesList->setFilterPattern(m_contentFilterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
|
||||
}
|
||||
|
||||
void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
|
@ -309,7 +336,14 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
|
|||
m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
|
||||
|
||||
m_ui->labelCreatedByVal->setText(m_torrent->creator());
|
||||
|
||||
m_ui->labelPrivateVal->setText(m_torrent->isPrivate() ? tr("Yes") : tr("No"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->labelPrivateVal->setText(tr("N/A"));
|
||||
}
|
||||
|
||||
// Load dynamic data
|
||||
loadDynamicData();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 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,7 +32,8 @@
|
|||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "gui/filterpatternformat.h"
|
||||
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
|
@ -102,6 +103,8 @@ private slots:
|
|||
|
||||
private:
|
||||
QPushButton *getButtonFromIndex(int index);
|
||||
void showContentFilterContextMenu();
|
||||
void setContentFilterPattern();
|
||||
|
||||
Ui::PropertiesWidget *m_ui = nullptr;
|
||||
BitTorrent::Torrent *m_torrent = nullptr;
|
||||
|
@ -115,4 +118,6 @@ private:
|
|||
PropTabBar *m_tabBar = nullptr;
|
||||
LineEdit *m_contentFilterLine = nullptr;
|
||||
int m_handleWidth = -1;
|
||||
|
||||
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||
};
|
||||
|
|
|
@ -823,6 +823,38 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelPrivate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Private:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelPrivateVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelInfohash1">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
|
@ -838,71 +870,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelInfohash2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Info Hash v2:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash2Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelSavePath">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Path:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelComment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash1Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
|
@ -918,7 +886,55 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelInfohash2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Info Hash v2:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash2Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelSavePath">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Path:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelSavePathVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
|
@ -937,7 +953,23 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="5">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="labelComment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelCommentVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 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
|
||||
|
@ -37,17 +37,12 @@
|
|||
#include <QPointer>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#else
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN || defined Q_OS_MACOS
|
||||
#if defined(Q_OS_MACOS)
|
||||
#define QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||
#include <QPixmapCache>
|
||||
#elif !defined(Q_OS_WIN)
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/downloadpriority.h"
|
||||
|
@ -116,27 +111,8 @@ namespace
|
|||
};
|
||||
#endif // QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
// See QTBUG-25319 for explanation why this is required
|
||||
class WinShellFileIconProvider final : public CachingFileIconProvider
|
||||
{
|
||||
QPixmap pixmapForExtension(const QString &ext) const override
|
||||
{
|
||||
const std::wstring extWStr = QString(u'.' + ext).toStdWString();
|
||||
|
||||
SHFILEINFOW sfi {};
|
||||
const HRESULT hr = ::SHGetFileInfoW(extWStr.c_str(),
|
||||
FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), (SHGFI_ICON | SHGFI_USEFILEATTRIBUTES));
|
||||
if (FAILED(hr))
|
||||
return {};
|
||||
|
||||
const auto iconPixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
||||
::DestroyIcon(sfi.hIcon);
|
||||
return iconPixmap;
|
||||
}
|
||||
};
|
||||
#elif defined(Q_OS_MACOS)
|
||||
// There is a similar bug on macOS, to be reported to Qt
|
||||
#if defined(Q_OS_MACOS)
|
||||
// There is a bug on macOS, to be reported to Qt
|
||||
// https://github.com/qbittorrent/qBittorrent/pull/6156#issuecomment-316302615
|
||||
class MacFileIconProvider final : public CachingFileIconProvider
|
||||
{
|
||||
|
@ -145,7 +121,7 @@ namespace
|
|||
return MacUtils::pixmapForExtension(ext, QSize(32, 32));
|
||||
}
|
||||
};
|
||||
#else
|
||||
#elif !defined(Q_OS_WIN)
|
||||
/**
|
||||
* @brief Tests whether QFileIconProvider actually works
|
||||
*
|
||||
|
@ -189,7 +165,7 @@ TorrentContentModel::TorrentContentModel(QObject *parent)
|
|||
: QAbstractItemModel(parent)
|
||||
, m_rootItem(new TorrentContentModelFolder(QVector<QString>({ tr("Name"), tr("Total Size"), tr("Progress"), tr("Download Priority"), tr("Remaining"), tr("Availability") })))
|
||||
#if defined(Q_OS_WIN)
|
||||
, m_fileIconProvider {new WinShellFileIconProvider}
|
||||
, m_fileIconProvider {new QFileIconProvider}
|
||||
#elif defined(Q_OS_MACOS)
|
||||
, m_fileIconProvider {new MacFileIconProvider}
|
||||
#else
|
||||
|
|
|
@ -147,10 +147,19 @@ void TorrentContentModelFolder::recalculateProgress()
|
|||
tRemaining += child->remaining();
|
||||
}
|
||||
|
||||
if (!isRootItem() && (tSize > 0))
|
||||
if (!isRootItem())
|
||||
{
|
||||
if (tSize > 0)
|
||||
{
|
||||
m_progress = tProgress / tSize;
|
||||
m_remaining = tRemaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_progress = 1.0;
|
||||
m_remaining = 0;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_progress <= 1.);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -56,6 +56,19 @@
|
|||
#include "gui/macutilities.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
QList<QPersistentModelIndex> toPersistentIndexes(const QModelIndexList &indexes)
|
||||
{
|
||||
QList<QPersistentModelIndex> persistentIndexes;
|
||||
persistentIndexes.reserve(indexes.size());
|
||||
for (const QModelIndex &index : indexes)
|
||||
persistentIndexes.emplaceBack(index);
|
||||
|
||||
return persistentIndexes;
|
||||
}
|
||||
}
|
||||
|
||||
TorrentContentWidget::TorrentContentWidget(QWidget *parent)
|
||||
: QTreeView(parent)
|
||||
{
|
||||
|
@ -173,10 +186,20 @@ Path TorrentContentWidget::getItemPath(const QModelIndex &index) const
|
|||
return path;
|
||||
}
|
||||
|
||||
void TorrentContentWidget::setFilterPattern(const QString &patternText)
|
||||
void TorrentContentWidget::setFilterPattern(const QString &patternText, const FilterPatternFormat format)
|
||||
{
|
||||
const QString pattern = Utils::String::wildcardToRegexPattern(patternText);
|
||||
if (format == FilterPatternFormat::PlainText)
|
||||
{
|
||||
m_filterModel->setFilterFixedString(patternText);
|
||||
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString pattern = ((format == FilterPatternFormat::Regex)
|
||||
? patternText : Utils::String::wildcardToRegexPattern(patternText));
|
||||
m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
}
|
||||
|
||||
if (patternText.isEmpty())
|
||||
{
|
||||
collapseAll();
|
||||
|
@ -219,9 +242,9 @@ void TorrentContentWidget::keyPressEvent(QKeyEvent *event)
|
|||
|
||||
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
|
||||
? Qt::Unchecked : Qt::Checked;
|
||||
const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME);
|
||||
const QList<QPersistentModelIndex> selection = toPersistentIndexes(selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME));
|
||||
|
||||
for (const QModelIndex &index : selection)
|
||||
for (const QPersistentModelIndex &index : selection)
|
||||
model()->setData(index, state, Qt::CheckStateRole);
|
||||
}
|
||||
|
||||
|
@ -248,10 +271,10 @@ void TorrentContentWidget::renameSelectedFile()
|
|||
|
||||
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
|
||||
{
|
||||
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
|
||||
for (const QModelIndex &index : selectedRows)
|
||||
const QList<QPersistentModelIndex> selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority));
|
||||
for (const QPersistentModelIndex &index : selectedRows)
|
||||
{
|
||||
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
|
||||
model()->setData(index, static_cast<int>(priority));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,7 +284,7 @@ void TorrentContentWidget::applyPrioritiesByOrder()
|
|||
// a download priority that will apply to each item. The number of groups depends on how
|
||||
// many "download priority" are available to be assigned
|
||||
|
||||
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
|
||||
const QList<QPersistentModelIndex> selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority));
|
||||
|
||||
const qsizetype priorityGroups = 3;
|
||||
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
|
||||
|
@ -283,8 +306,8 @@ void TorrentContentWidget::applyPrioritiesByOrder()
|
|||
break;
|
||||
}
|
||||
|
||||
const QModelIndex &index = selectedRows[i];
|
||||
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
|
||||
const QPersistentModelIndex &index = selectedRows[i];
|
||||
model()->setData(index, static_cast<int>(priority));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include "base/bittorrent/downloadpriority.h"
|
||||
#include "base/pathfwd.h"
|
||||
#include "filterpatternformat.h"
|
||||
|
||||
class QShortcut;
|
||||
|
||||
|
@ -92,7 +93,7 @@ public:
|
|||
int getFileIndex(const QModelIndex &index) const;
|
||||
Path getItemPath(const QModelIndex &index) const;
|
||||
|
||||
void setFilterPattern(const QString &patternText);
|
||||
void setFilterPattern(const QString &patternText, FilterPatternFormat format = FilterPatternFormat::Wildcards);
|
||||
|
||||
void checkAll();
|
||||
void checkNone();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2024 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
|
||||
|
@ -37,6 +37,7 @@
|
|||
#include "base/global.h"
|
||||
#include "autoexpandabledialog.h"
|
||||
#include "flowlayout.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "ui_torrenttagsdialog.h"
|
||||
|
||||
|
@ -52,10 +53,10 @@ TorrentTagsDialog::TorrentTagsDialog(const TagSet &initialTags, QWidget *parent)
|
|||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
auto *tagsLayout = new FlowLayout(m_ui->scrollArea);
|
||||
auto *tagsLayout = new FlowLayout(m_ui->scrollArea->widget());
|
||||
for (const Tag &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags())))
|
||||
{
|
||||
auto *tagWidget = new QCheckBox(tag.toString());
|
||||
auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag));
|
||||
if (initialTags.contains(tag))
|
||||
tagWidget->setChecked(true);
|
||||
tagsLayout->addWidget(tagWidget);
|
||||
|
@ -78,12 +79,12 @@ TorrentTagsDialog::~TorrentTagsDialog()
|
|||
TagSet TorrentTagsDialog::tags() const
|
||||
{
|
||||
TagSet tags;
|
||||
auto *layout = m_ui->scrollArea->layout();
|
||||
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||
for (int i = 0; i < (layout->count() - 1); ++i)
|
||||
{
|
||||
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
|
||||
if (tagWidget->isChecked())
|
||||
tags.insert(Tag(tagWidget->text()));
|
||||
tags.insert(Utils::Gui::widgetTextToTag(tagWidget->text()));
|
||||
}
|
||||
|
||||
return tags;
|
||||
|
@ -111,9 +112,9 @@ void TorrentTagsDialog::addNewTag()
|
|||
}
|
||||
else
|
||||
{
|
||||
auto *layout = m_ui->scrollArea->layout();
|
||||
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||
auto *btn = layout->takeAt(layout->count() - 1);
|
||||
auto *tagWidget = new QCheckBox(tag.toString());
|
||||
auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag));
|
||||
tagWidget->setChecked(true);
|
||||
layout->addWidget(tagWidget);
|
||||
layout->addItem(btn);
|
||||
|
|
|
@ -488,11 +488,11 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
|||
switch (section)
|
||||
{
|
||||
case COL_URL:
|
||||
return tr("URL/Announce endpoint");
|
||||
return tr("URL/Announce Endpoint");
|
||||
case COL_TIER:
|
||||
return tr("Tier");
|
||||
case COL_PROTOCOL:
|
||||
return tr("Protocol");
|
||||
return tr("BT Protocol");
|
||||
case COL_STATUS:
|
||||
return tr("Status");
|
||||
case COL_PEERS:
|
||||
|
@ -506,9 +506,9 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
|||
case COL_MSG:
|
||||
return tr("Message");
|
||||
case COL_NEXT_ANNOUNCE:
|
||||
return tr("Next announce");
|
||||
return tr("Next Announce");
|
||||
case COL_MIN_ANNOUNCE:
|
||||
return tr("Min announce");
|
||||
return tr("Min Announce");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -585,7 +585,7 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const
|
|||
case COL_TIER:
|
||||
return (isEndpoint || (index.row() < STICKY_ROW_COUNT)) ? QString() : QString::number(itemPtr->tier);
|
||||
case COL_PROTOCOL:
|
||||
return isEndpoint ? tr("v%1").arg(itemPtr->btVersion) : QString();
|
||||
return isEndpoint ? (u'v' + QString::number(itemPtr->btVersion)) : QString();
|
||||
case COL_STATUS:
|
||||
if (isEndpoint)
|
||||
return toString(itemPtr->status);
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
|
|
|
@ -193,6 +193,7 @@ QVariant TransferListModel::headerData(const int section, const Qt::Orientation
|
|||
case TR_INFOHASH_V1: return tr("Info Hash v1", "i.e: torrent info hash v1");
|
||||
case TR_INFOHASH_V2: return tr("Info Hash v2", "i.e: torrent info hash v2");
|
||||
case TR_REANNOUNCE: return tr("Reannounce In", "Indicates the time until next trackers reannounce");
|
||||
case TR_PRIVATE: return tr("Private", "Flags private torrents");
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
@ -357,6 +358,15 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
|
|||
return Utils::Misc::userFriendlyDuration(time);
|
||||
};
|
||||
|
||||
const auto privateString = [hideValues](const bool isPrivate, const bool hasMetadata) -> QString
|
||||
{
|
||||
if (hideValues && !isPrivate)
|
||||
return {};
|
||||
if (hasMetadata)
|
||||
return isPrivate ? tr("Yes") : tr("No");
|
||||
return tr("N/A");
|
||||
};
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case TR_NAME:
|
||||
|
@ -431,6 +441,8 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
|
|||
return hashString(torrent->infoHash().v2());
|
||||
case TR_REANNOUNCE:
|
||||
return reannounceString(torrent->nextAnnounce());
|
||||
case TR_PRIVATE:
|
||||
return privateString(torrent->isPrivate(), torrent->hasMetadata());
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -512,6 +524,8 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co
|
|||
return QVariant::fromValue(torrent->infoHash().v2());
|
||||
case TR_REANNOUNCE:
|
||||
return torrent->nextAnnounce();
|
||||
case TR_PRIVATE:
|
||||
return (torrent->hasMetadata() ? torrent->isPrivate() : QVariant());
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
TR_INFOHASH_V1,
|
||||
TR_INFOHASH_V2,
|
||||
TR_REANNOUNCE,
|
||||
TR_PRIVATE,
|
||||
|
||||
NB_COLUMNS
|
||||
};
|
||||
|
|
|
@ -84,6 +84,17 @@ namespace
|
|||
return isLeftValid ? -1 : 1;
|
||||
}
|
||||
|
||||
int compareAsBool(const QVariant &left, const QVariant &right)
|
||||
{
|
||||
const bool leftValid = left.isValid();
|
||||
const bool rightValid = right.isValid();
|
||||
if (leftValid && rightValid)
|
||||
return threeWayCompare(left.toBool(), right.toBool());
|
||||
if (!leftValid && !rightValid)
|
||||
return 0;
|
||||
return leftValid ? -1 : 1;
|
||||
}
|
||||
|
||||
int adjustSubSortColumn(const int column)
|
||||
{
|
||||
return ((column >= 0) && (column < TransferListModel::NB_COLUMNS))
|
||||
|
@ -214,6 +225,9 @@ int TransferListSortModel::compare(const QModelIndex &left, const QModelIndex &r
|
|||
case TransferListModel::TR_UPSPEED:
|
||||
return customCompare(leftValue.toInt(), rightValue.toInt());
|
||||
|
||||
case TransferListModel::TR_PRIVATE:
|
||||
return compareAsBool(leftValue, rightValue);
|
||||
|
||||
case TransferListModel::TR_PEERS:
|
||||
case TransferListModel::TR_SEEDS:
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -116,9 +116,10 @@ namespace
|
|||
void removeTorrents(const QVector<BitTorrent::Torrent *> &torrents, const bool isDeleteFileSelected)
|
||||
{
|
||||
auto *session = BitTorrent::Session::instance();
|
||||
const DeleteOption deleteOption = isDeleteFileSelected ? DeleteTorrentAndFiles : DeleteTorrent;
|
||||
const BitTorrent::TorrentRemoveOption removeOption = isDeleteFileSelected
|
||||
? BitTorrent::TorrentRemoveOption::RemoveContent : BitTorrent::TorrentRemoveOption::KeepContent;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
session->deleteTorrent(torrent->id(), deleteOption);
|
||||
session->removeTorrent(torrent->id(), removeOption);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,6 +184,7 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow)
|
|||
setColumnHidden(TransferListModel::TR_LAST_ACTIVITY, true);
|
||||
setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true);
|
||||
setColumnHidden(TransferListModel::TR_REANNOUNCE, true);
|
||||
setColumnHidden(TransferListModel::TR_PRIVATE, true);
|
||||
}
|
||||
|
||||
//Ensure that at least one column is visible at all times
|
||||
|
@ -442,7 +444,7 @@ void TransferListWidget::deleteSelectedTorrents(const bool deleteLocalFiles)
|
|||
{
|
||||
// Some torrents might be removed when waiting for user input, so refetch the torrent list
|
||||
// NOTE: this will only work when dialog is modal
|
||||
removeTorrents(getSelectedTorrents(), dialog->isDeleteFileSelected());
|
||||
removeTorrents(getSelectedTorrents(), dialog->isRemoveContentSelected());
|
||||
});
|
||||
dialog->open();
|
||||
}
|
||||
|
@ -465,7 +467,7 @@ void TransferListWidget::deleteVisibleTorrents()
|
|||
{
|
||||
// Some torrents might be removed when waiting for user input, so refetch the torrent list
|
||||
// NOTE: this will only work when dialog is modal
|
||||
removeTorrents(getVisibleTorrents(), dialog->isDeleteFileSelected());
|
||||
removeTorrents(getVisibleTorrents(), dialog->isRemoveContentSelected());
|
||||
});
|
||||
dialog->open();
|
||||
}
|
||||
|
@ -1190,7 +1192,7 @@ void TransferListWidget::displayListMenu()
|
|||
const TagSet tags = BitTorrent::Session::instance()->tags();
|
||||
for (const Tag &tag : asConst(tags))
|
||||
{
|
||||
auto *action = new TriStateAction(tag.toString(), tagsMenu);
|
||||
auto *action = new TriStateAction(Utils::Gui::tagToWidgetText(tag), tagsMenu);
|
||||
action->setCloseOnInteraction(false);
|
||||
|
||||
const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017 Mike Tzou
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -54,6 +55,7 @@
|
|||
|
||||
#include "base/global.h"
|
||||
#include "base/path.h"
|
||||
#include "base/tag.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/version.h"
|
||||
|
||||
|
@ -216,3 +218,29 @@ void Utils::Gui::openFolderSelect(const Path &path)
|
|||
openPath(path.parentPath());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Utils::Gui::tagToWidgetText(const Tag &tag)
|
||||
{
|
||||
return tag.toString().replace(u'&', u"&&"_s);
|
||||
}
|
||||
|
||||
Tag Utils::Gui::widgetTextToTag(const QString &text)
|
||||
{
|
||||
// replace pairs of '&' with single '&' and remove non-paired occurrences of '&'
|
||||
QString cleanedText;
|
||||
cleanedText.reserve(text.size());
|
||||
bool amp = false;
|
||||
for (const QChar c : text)
|
||||
{
|
||||
if (c == u'&')
|
||||
{
|
||||
amp = !amp;
|
||||
if (amp)
|
||||
continue;
|
||||
}
|
||||
|
||||
cleanedText.append(c);
|
||||
}
|
||||
|
||||
return Tag(cleanedText);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017 Mike Tzou
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -34,8 +35,11 @@ class QIcon;
|
|||
class QPixmap;
|
||||
class QPoint;
|
||||
class QSize;
|
||||
class QString;
|
||||
class QWidget;
|
||||
|
||||
class Tag;
|
||||
|
||||
namespace Utils::Gui
|
||||
{
|
||||
bool isDarkTheme();
|
||||
|
@ -51,4 +55,7 @@ namespace Utils::Gui
|
|||
|
||||
void openPath(const Path &path);
|
||||
void openFolderSelect(const Path &path);
|
||||
|
||||
QString tagToWidgetText(const Tag &tag);
|
||||
Tag widgetTextToTag(const QString &text);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
name="org.qbittorrent.qBittorrent"
|
||||
version="1.0.0.0"
|
||||
processorArchitecture="*"
|
||||
type="win32"
|
||||
/>
|
||||
|
||||
<!-- Enable use of version 6 of the common controls (Win XP and later) -->
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
|
@ -28,6 +35,7 @@
|
|||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Enable long paths that exceed MAX_PATH in length -->
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#VERSION: 1.45
|
||||
#VERSION: 1.47
|
||||
|
||||
# Author:
|
||||
# Christophe DUMEZ (chris@qbittorrent.org)
|
||||
|
@ -39,9 +39,11 @@ import tempfile
|
|||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
def getBrowserUserAgent():
|
||||
def getBrowserUserAgent() -> str:
|
||||
""" Disguise as browser to circumvent website blocking """
|
||||
|
||||
# Firefox release calendar
|
||||
|
@ -57,7 +59,7 @@ def getBrowserUserAgent():
|
|||
return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:{nowVersion}.0) Gecko/20100101 Firefox/{nowVersion}.0"
|
||||
|
||||
|
||||
headers = {'User-Agent': getBrowserUserAgent()}
|
||||
headers: Dict[str, Any] = {'User-Agent': getBrowserUserAgent()}
|
||||
|
||||
# SOCKS5 Proxy support
|
||||
if "sock_proxy" in os.environ and len(os.environ["sock_proxy"].strip()) > 0:
|
||||
|
@ -67,13 +69,13 @@ if "sock_proxy" in os.environ and len(os.environ["sock_proxy"].strip()) > 0:
|
|||
if m is not None:
|
||||
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, m.group('host'),
|
||||
int(m.group('port')), True, m.group('username'), m.group('password'))
|
||||
socket.socket = socks.socksocket
|
||||
socket.socket = socks.socksocket # type: ignore[misc]
|
||||
|
||||
|
||||
def htmlentitydecode(s):
|
||||
def htmlentitydecode(s: str) -> str:
|
||||
# First convert alpha entities (such as é)
|
||||
# (Inspired from http://mail.python.org/pipermail/python-list/2007-June/443813.html)
|
||||
def entity2char(m):
|
||||
def entity2char(m: re.Match[str]) -> str:
|
||||
entity = m.group(1)
|
||||
if entity in html.entities.name2codepoint:
|
||||
return chr(html.entities.name2codepoint[entity])
|
||||
|
@ -87,15 +89,15 @@ def htmlentitydecode(s):
|
|||
return re.sub(r'&#x(\w+);', lambda x: chr(int(x.group(1), 16)), t)
|
||||
|
||||
|
||||
def retrieve_url(url):
|
||||
def retrieve_url(url: str, custom_headers: Mapping[str, Any] = {}) -> str:
|
||||
""" Return the content of the url page as a string """
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
req = urllib.request.Request(url, headers={**headers, **custom_headers})
|
||||
try:
|
||||
response = urllib.request.urlopen(req)
|
||||
except urllib.error.URLError as errno:
|
||||
print(" ".join(("Connection error:", str(errno.reason))))
|
||||
return ""
|
||||
dat = response.read()
|
||||
dat: bytes = response.read()
|
||||
# Check if it is gzipped
|
||||
if dat[:2] == b'\x1f\x8b':
|
||||
# Data is gzip encoded, decode it
|
||||
|
@ -109,16 +111,15 @@ def retrieve_url(url):
|
|||
ignore, charset = info['Content-Type'].split('charset=')
|
||||
except Exception:
|
||||
pass
|
||||
dat = dat.decode(charset, 'replace')
|
||||
dat = htmlentitydecode(dat)
|
||||
# return dat.encode('utf-8', 'replace')
|
||||
return dat
|
||||
datStr = dat.decode(charset, 'replace')
|
||||
datStr = htmlentitydecode(datStr)
|
||||
return datStr
|
||||
|
||||
|
||||
def download_file(url, referer=None):
|
||||
def download_file(url: str, referer: Optional[str] = None) -> str:
|
||||
""" Download file at url and write it to a file, return the path to the file and the url """
|
||||
file, path = tempfile.mkstemp()
|
||||
file = os.fdopen(file, "wb")
|
||||
fileHandle, path = tempfile.mkstemp()
|
||||
file = os.fdopen(fileHandle, "wb")
|
||||
# Download url
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
if referer is not None:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#VERSION: 1.45
|
||||
#VERSION: 1.46
|
||||
|
||||
# Author:
|
||||
# Fabien Devaux <fab AT gnux DOT info>
|
||||
|
@ -37,17 +37,21 @@ import importlib
|
|||
import pathlib
|
||||
import sys
|
||||
import urllib.parse
|
||||
from collections.abc import Iterable, Iterator, Sequence
|
||||
from enum import Enum
|
||||
from glob import glob
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from os import path
|
||||
from typing import Dict, List, Optional, Set, Tuple, Type
|
||||
|
||||
THREADED = True
|
||||
THREADED: bool = True
|
||||
try:
|
||||
MAX_THREADS = cpu_count()
|
||||
MAX_THREADS: int = cpu_count()
|
||||
except NotImplementedError:
|
||||
MAX_THREADS = 1
|
||||
|
||||
CATEGORIES = {'all', 'movies', 'tv', 'music', 'games', 'anime', 'software', 'pictures', 'books'}
|
||||
Category = Enum('Category', ['all', 'movies', 'tv', 'music', 'games', 'anime', 'software', 'pictures', 'books'])
|
||||
|
||||
|
||||
################################################################################
|
||||
# Every engine should have a "search" method taking
|
||||
|
@ -58,11 +62,29 @@ CATEGORIES = {'all', 'movies', 'tv', 'music', 'games', 'anime', 'software', 'pic
|
|||
################################################################################
|
||||
|
||||
|
||||
EngineName = str
|
||||
|
||||
|
||||
class Engine:
|
||||
url: str
|
||||
name: EngineName
|
||||
supported_categories: Dict[str, str]
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def search(self, what: str, cat: str = Category.all.name) -> None:
|
||||
pass
|
||||
|
||||
def download_torrent(self, info: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
# global state
|
||||
engine_dict = dict()
|
||||
engine_dict: Dict[EngineName, Optional[Type[Engine]]] = {}
|
||||
|
||||
|
||||
def list_engines():
|
||||
def list_engines() -> List[EngineName]:
|
||||
""" List all engines,
|
||||
including broken engines that fail on import
|
||||
|
||||
|
@ -81,10 +103,10 @@ def list_engines():
|
|||
return found_engines
|
||||
|
||||
|
||||
def get_engine(engine_name):
|
||||
#global engine_dict
|
||||
def get_engine(engine_name: EngineName) -> Optional[Type[Engine]]:
|
||||
if engine_name in engine_dict:
|
||||
return engine_dict[engine_name]
|
||||
|
||||
# when import fails, engine is None
|
||||
engine = None
|
||||
try:
|
||||
|
@ -97,35 +119,37 @@ def get_engine(engine_name):
|
|||
return engine
|
||||
|
||||
|
||||
def initialize_engines(found_engines):
|
||||
def initialize_engines(found_engines: Iterable[EngineName]) -> Set[EngineName]:
|
||||
""" Import available engines
|
||||
|
||||
Return list of available engines
|
||||
Return set of available engines
|
||||
"""
|
||||
supported_engines = []
|
||||
supported_engines = set()
|
||||
|
||||
for engine_name in found_engines:
|
||||
# import engine
|
||||
engine = get_engine(engine_name)
|
||||
if engine is None:
|
||||
continue
|
||||
supported_engines.append(engine_name)
|
||||
supported_engines.add(engine_name)
|
||||
|
||||
return supported_engines
|
||||
|
||||
|
||||
def engines_to_xml(supported_engines):
|
||||
def engines_to_xml(supported_engines: Iterable[EngineName]) -> Iterator[str]:
|
||||
""" Generates xml for supported engines """
|
||||
tab = " " * 4
|
||||
|
||||
for engine_name in supported_engines:
|
||||
search_engine = get_engine(engine_name)
|
||||
if search_engine is None:
|
||||
continue
|
||||
|
||||
supported_categories = ""
|
||||
if hasattr(search_engine, "supported_categories"):
|
||||
supported_categories = " ".join((key
|
||||
for key in search_engine.supported_categories.keys()
|
||||
if key != "all"))
|
||||
if key != Category.all.name))
|
||||
|
||||
yield "".join((tab, "<", engine_name, ">\n",
|
||||
tab, tab, "<name>", search_engine.name, "</name>\n",
|
||||
|
@ -134,7 +158,7 @@ def engines_to_xml(supported_engines):
|
|||
tab, "</", engine_name, ">\n"))
|
||||
|
||||
|
||||
def displayCapabilities(supported_engines):
|
||||
def displayCapabilities(supported_engines: Iterable[EngineName]) -> None:
|
||||
"""
|
||||
Display capabilities in XML format
|
||||
<capabilities>
|
||||
|
@ -151,21 +175,24 @@ def displayCapabilities(supported_engines):
|
|||
print(xml)
|
||||
|
||||
|
||||
def run_search(engine_list):
|
||||
def run_search(engine_list: Tuple[Optional[Type[Engine]], str, Category]) -> bool:
|
||||
""" Run search in engine
|
||||
|
||||
@param engine_list List with engine, query and category
|
||||
@param engine_list Tuple with engine, query and category
|
||||
|
||||
@retval False if any exceptions occurred
|
||||
@retval True otherwise
|
||||
"""
|
||||
engine, what, cat = engine_list
|
||||
engine_class, what, cat = engine_list
|
||||
if engine_class is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
engine = engine()
|
||||
engine = engine_class()
|
||||
# avoid exceptions due to invalid category
|
||||
if hasattr(engine, 'supported_categories'):
|
||||
if cat in engine.supported_categories:
|
||||
engine.search(what, cat)
|
||||
if cat.name in engine.supported_categories:
|
||||
engine.search(what, cat.name)
|
||||
else:
|
||||
engine.search(what)
|
||||
|
||||
|
@ -174,7 +201,7 @@ def run_search(engine_list):
|
|||
return False
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: Sequence[str]) -> None:
|
||||
# qbt tend to run this script in 'isolate mode' so append the current path manually
|
||||
current_path = str(pathlib.Path(__file__).parent.resolve())
|
||||
if current_path not in sys.path:
|
||||
|
@ -182,7 +209,7 @@ def main(args):
|
|||
|
||||
found_engines = list_engines()
|
||||
|
||||
def show_usage():
|
||||
def show_usage() -> None:
|
||||
print("./nova2.py all|engine1[,engine2]* <category> <keywords>", file=sys.stderr)
|
||||
print("found engines: " + ','.join(found_engines), file=sys.stderr)
|
||||
print("to list available engines: ./nova2.py --capabilities [--names]", file=sys.stderr)
|
||||
|
@ -190,7 +217,6 @@ def main(args):
|
|||
if not args:
|
||||
show_usage()
|
||||
sys.exit(1)
|
||||
|
||||
elif args[0] == "--capabilities":
|
||||
supported_engines = initialize_engines(found_engines)
|
||||
if "--names" in args:
|
||||
|
@ -198,14 +224,14 @@ def main(args):
|
|||
return
|
||||
displayCapabilities(supported_engines)
|
||||
return
|
||||
|
||||
elif len(args) < 3:
|
||||
show_usage()
|
||||
sys.exit(1)
|
||||
|
||||
cat = args[1].lower()
|
||||
|
||||
if cat not in CATEGORIES:
|
||||
try:
|
||||
category = Category[cat]
|
||||
except KeyError:
|
||||
print(" - ".join(('Invalid category', cat)), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -223,16 +249,18 @@ def main(args):
|
|||
engines_list = initialize_engines(found_engines)
|
||||
else:
|
||||
# discard not-found engines
|
||||
engines_list = [engine for engine in engines_list if engine in found_engines]
|
||||
engines_list = {engine for engine in engines_list if engine in found_engines}
|
||||
|
||||
what = urllib.parse.quote(' '.join(args[2:]))
|
||||
params = ((get_engine(engine_name), what, category) for engine_name in engines_list)
|
||||
|
||||
if THREADED:
|
||||
# child process spawning is controlled min(number of searches, number of cpu)
|
||||
with Pool(min(len(engines_list), MAX_THREADS)) as pool:
|
||||
pool.map(run_search, ([get_engine(engine_name), what, cat] for engine_name in engines_list))
|
||||
pool.map(run_search, params)
|
||||
else:
|
||||
# py3 note: map is needed to be evaluated for content to be executed
|
||||
all(map(run_search, ([get_engine(engine_name), what, cat] for engine_name in engines_list)))
|
||||
all(map(run_search, params))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#VERSION: 1.48
|
||||
#VERSION: 1.50
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
|
@ -24,8 +24,25 @@
|
|||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Union
|
||||
|
||||
def prettyPrinter(dictionary):
|
||||
# TODO: enable the following when using Python >= 3.8
|
||||
#SearchResults = TypedDict('SearchResults', {
|
||||
# 'link': str,
|
||||
# 'name': str,
|
||||
# 'size': Union[float, int, str],
|
||||
# 'seeds': int,
|
||||
# 'leech': int,
|
||||
# 'engine_url': str,
|
||||
# 'desc_link': str, # Optional # TODO: use `NotRequired[str]` when using Python >= 3.11
|
||||
# 'pub_date': int # Optional # TODO: use `NotRequired[int]` when using Python >= 3.11
|
||||
#})
|
||||
SearchResults = Mapping[str, Any]
|
||||
|
||||
|
||||
def prettyPrinter(dictionary: SearchResults) -> None:
|
||||
outtext = "|".join((
|
||||
dictionary["link"],
|
||||
dictionary["name"].replace("|", " "),
|
||||
|
@ -34,7 +51,7 @@ def prettyPrinter(dictionary):
|
|||
str(dictionary["leech"]),
|
||||
dictionary["engine_url"],
|
||||
dictionary.get("desc_link", ""), # Optional
|
||||
str(dictionary.get("pub_date", -1)), # Optional
|
||||
str(dictionary.get("pub_date", -1)) # Optional
|
||||
))
|
||||
|
||||
# fd 1 is stdout
|
||||
|
@ -42,30 +59,32 @@ def prettyPrinter(dictionary):
|
|||
print(outtext, file=utf8stdout)
|
||||
|
||||
|
||||
def anySizeToBytes(size_string):
|
||||
sizeUnitRegex: re.Pattern[str] = re.compile(r"^(?P<size>\d*\.?\d+) *(?P<unit>[a-z]+)?", re.IGNORECASE)
|
||||
|
||||
|
||||
def anySizeToBytes(size_string: Union[float, int, str]) -> int:
|
||||
"""
|
||||
Convert a string like '1 KB' to '1024' (bytes)
|
||||
"""
|
||||
# separate integer from unit
|
||||
try:
|
||||
size, unit = size_string.split()
|
||||
except Exception:
|
||||
try:
|
||||
size = size_string.strip()
|
||||
unit = ''.join([c for c in size if c.isalpha()])
|
||||
if len(unit) > 0:
|
||||
size = size[:-len(unit)]
|
||||
except Exception:
|
||||
return -1
|
||||
if len(size) == 0:
|
||||
return -1
|
||||
size = float(size)
|
||||
if len(unit) == 0:
|
||||
return int(size)
|
||||
short_unit = unit.upper()[0]
|
||||
|
||||
# convert
|
||||
units_dict = {'T': 40, 'G': 30, 'M': 20, 'K': 10}
|
||||
if short_unit in units_dict:
|
||||
size = size * 2**units_dict[short_unit]
|
||||
return int(size)
|
||||
The canonical type for `size_string` is `str`. However numeric types are also accepted in order to
|
||||
accommodate poorly written plugins.
|
||||
"""
|
||||
|
||||
if isinstance(size_string, int):
|
||||
return size_string
|
||||
if isinstance(size_string, float):
|
||||
return round(size_string)
|
||||
|
||||
match = sizeUnitRegex.match(size_string.strip())
|
||||
if match is None:
|
||||
return -1
|
||||
|
||||
size = float(match.group('size')) # need to match decimals
|
||||
unit = match.group('unit')
|
||||
|
||||
if unit is not None:
|
||||
units_exponents = {'T': 40, 'G': 30, 'M': 20, 'K': 10}
|
||||
exponent = units_exponents.get(unit[0].upper(), 0)
|
||||
size *= 2**exponent
|
||||
|
||||
return round(size)
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
@ -135,7 +136,7 @@ void AppController::preferencesAction()
|
|||
data[u"file_log_age"_s] = app()->fileLoggerAge();
|
||||
data[u"file_log_age_type"_s] = app()->fileLoggerAgeType();
|
||||
// Delete torrent contents files on torrent removal
|
||||
data[u"delete_torrent_content_files"_s] = pref->deleteTorrentFilesAsDefault();
|
||||
data[u"delete_torrent_content_files"_s] = pref->removeTorrentContent();
|
||||
|
||||
// Downloads
|
||||
// When adding a torrent
|
||||
|
@ -349,6 +350,8 @@ void AppController::preferencesAction()
|
|||
// qBitorrent preferences
|
||||
// Resume data storage type
|
||||
data[u"resume_data_storage_type"_s] = Utils::String::fromEnum(session->resumeDataStorageType());
|
||||
// Torrent content removing mode
|
||||
data[u"torrent_content_remove_option"_s] = Utils::String::fromEnum(session->torrentContentRemoveOption());
|
||||
// Physical memory (RAM) usage limit
|
||||
data[u"memory_working_set_limit"_s] = app()->memoryWorkingSetLimit();
|
||||
// Current network interface
|
||||
|
@ -518,7 +521,7 @@ void AppController::setPreferencesAction()
|
|||
app()->setFileLoggerAgeType(it.value().toInt());
|
||||
// Delete torrent content files on torrent removal
|
||||
if (hasKey(u"delete_torrent_content_files"_s))
|
||||
pref->setDeleteTorrentFilesAsDefault(it.value().toBool());
|
||||
pref->setRemoveTorrentContent(it.value().toBool());
|
||||
|
||||
// Downloads
|
||||
// When adding a torrent
|
||||
|
@ -930,6 +933,9 @@ void AppController::setPreferencesAction()
|
|||
// Resume data storage type
|
||||
if (hasKey(u"resume_data_storage_type"_s))
|
||||
session->setResumeDataStorageType(Utils::String::toEnum(it.value().toString(), BitTorrent::ResumeDataStorageType::Legacy));
|
||||
// Torrent content removing mode
|
||||
if (hasKey(u"torrent_content_remove_option"_s))
|
||||
session->setTorrentContentRemoveOption(Utils::String::toEnum(it.value().toString(), BitTorrent::TorrentContentRemoveOption::MoveToTrash));
|
||||
// Physical memory (RAM) usage limit
|
||||
if (hasKey(u"memory_working_set_limit"_s))
|
||||
app()->setMemoryWorkingSetLimit(it.value().toInt());
|
||||
|
@ -1159,8 +1165,11 @@ void AppController::getDirectoryContentAction()
|
|||
throw APIError(APIErrorType::BadParams, tr("Invalid mode, allowed values: %1").arg(u"all, dirs, files"_s));
|
||||
};
|
||||
|
||||
const QStringList dirs = dir.entryList(QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility));
|
||||
setResult(QJsonArray::fromStringList(dirs));
|
||||
QJsonArray ret;
|
||||
QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
|
||||
while (it.hasNext())
|
||||
ret.append(it.next());
|
||||
setResult(ret);
|
||||
}
|
||||
|
||||
void AppController::networkInterfaceListAction()
|
||||
|
|
|
@ -135,6 +135,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
|
|||
{KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()},
|
||||
{KEY_TORRENT_DOWNLOAD_PATH, torrent.downloadPath().toString()},
|
||||
{KEY_TORRENT_CONTENT_PATH, torrent.contentPath().toString()},
|
||||
{KEY_TORRENT_ROOT_PATH, torrent.rootPath().toString()},
|
||||
{KEY_TORRENT_ADDED_ON, Utils::DateTime::toSecsSinceEpoch(torrent.addedTime())},
|
||||
{KEY_TORRENT_COMPLETION_ON, Utils::DateTime::toSecsSinceEpoch(torrent.completedTime())},
|
||||
{KEY_TORRENT_TRACKER, torrent.currentTracker()},
|
||||
|
@ -163,8 +164,8 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
|
|||
{KEY_TORRENT_AVAILABILITY, torrent.distributedCopies()},
|
||||
{KEY_TORRENT_REANNOUNCE, torrent.nextAnnounce()},
|
||||
{KEY_TORRENT_COMMENT, torrent.comment()},
|
||||
{KEY_TORRENT_ISPRIVATE, torrent.isPrivate()},
|
||||
|
||||
{KEY_TORRENT_TOTAL_SIZE, torrent.totalSize()}
|
||||
{KEY_TORRENT_PRIVATE, (torrent.hasMetadata() ? torrent.isPrivate() : QVariant())},
|
||||
{KEY_TORRENT_TOTAL_SIZE, torrent.totalSize()},
|
||||
{KEY_TORRENT_HAS_METADATA, torrent.hasMetadata()}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ inline const QString KEY_TORRENT_FORCE_START = u"force_start"_s;
|
|||
inline const QString KEY_TORRENT_SAVE_PATH = u"save_path"_s;
|
||||
inline const QString KEY_TORRENT_DOWNLOAD_PATH = u"download_path"_s;
|
||||
inline const QString KEY_TORRENT_CONTENT_PATH = u"content_path"_s;
|
||||
inline const QString KEY_TORRENT_ROOT_PATH = u"root_path"_s;
|
||||
inline const QString KEY_TORRENT_ADDED_ON = u"added_on"_s;
|
||||
inline const QString KEY_TORRENT_COMPLETION_ON = u"completion_on"_s;
|
||||
inline const QString KEY_TORRENT_TRACKER = u"tracker"_s;
|
||||
|
@ -93,6 +94,7 @@ inline const QString KEY_TORRENT_SEEDING_TIME = u"seeding_time"_s;
|
|||
inline const QString KEY_TORRENT_AVAILABILITY = u"availability"_s;
|
||||
inline const QString KEY_TORRENT_REANNOUNCE = u"reannounce"_s;
|
||||
inline const QString KEY_TORRENT_COMMENT = u"comment"_s;
|
||||
inline const QString KEY_TORRENT_ISPRIVATE = u"is_private"_s;
|
||||
inline const QString KEY_TORRENT_PRIVATE = u"private"_s;
|
||||
inline const QString KEY_TORRENT_HAS_METADATA = u"has_metadata"_s;
|
||||
|
||||
QVariantMap serialize(const BitTorrent::Torrent &torrent);
|
||||
|
|
|
@ -222,6 +222,7 @@ namespace
|
|||
case QMetaType::UInt:
|
||||
case QMetaType::QDateTime:
|
||||
case QMetaType::Nullptr:
|
||||
case QMetaType::UnknownType:
|
||||
if (prevData[key] != value)
|
||||
syncData[key] = value;
|
||||
break;
|
||||
|
|
|
@ -111,10 +111,13 @@ const QString KEY_PROP_CREATION_DATE = u"creation_date"_s;
|
|||
const QString KEY_PROP_SAVE_PATH = u"save_path"_s;
|
||||
const QString KEY_PROP_DOWNLOAD_PATH = u"download_path"_s;
|
||||
const QString KEY_PROP_COMMENT = u"comment"_s;
|
||||
const QString KEY_PROP_ISPRIVATE = u"is_private"_s;
|
||||
const QString KEY_PROP_IS_PRIVATE = u"is_private"_s; // deprecated, "private" should be used instead
|
||||
const QString KEY_PROP_PRIVATE = u"private"_s;
|
||||
const QString KEY_PROP_SSL_CERTIFICATE = u"ssl_certificate"_s;
|
||||
const QString KEY_PROP_SSL_PRIVATEKEY = u"ssl_private_key"_s;
|
||||
const QString KEY_PROP_SSL_DHPARAMS = u"ssl_dh_params"_s;
|
||||
const QString KEY_PROP_HAS_METADATA = u"has_metadata"_s;
|
||||
|
||||
|
||||
// File keys
|
||||
const QString KEY_FILE_INDEX = u"index"_s;
|
||||
|
@ -282,6 +285,7 @@ void TorrentsController::countAction()
|
|||
// - category (string): torrent category for filtering by it (empty string means "uncategorized"; no "category" param presented means "any category")
|
||||
// - tag (string): torrent tag for filtering by it (empty string means "untagged"; no "tag" param presented means "any tag")
|
||||
// - hashes (string): filter by hashes, can contain multiple hashes separated by |
|
||||
// - private (bool): filter torrents that are from private trackers (true) or not (false). Empty means any torrent (no filtering)
|
||||
// - sort (string): name of column for sorting by its value
|
||||
// - reverse (bool): enable reverse sorting
|
||||
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
|
||||
|
@ -296,6 +300,7 @@ void TorrentsController::infoAction()
|
|||
int limit {params()[u"limit"_s].toInt()};
|
||||
int offset {params()[u"offset"_s].toInt()};
|
||||
const QStringList hashes {params()[u"hashes"_s].split(u'|', Qt::SkipEmptyParts)};
|
||||
const std::optional<bool> isPrivate = parseBool(params()[u"private"_s]);
|
||||
|
||||
std::optional<TorrentIDSet> idSet;
|
||||
if (!hashes.isEmpty())
|
||||
|
@ -305,7 +310,7 @@ void TorrentsController::infoAction()
|
|||
idSet->insert(BitTorrent::TorrentID::fromString(hash));
|
||||
}
|
||||
|
||||
const TorrentFilter torrentFilter {filter, idSet, category, tag};
|
||||
const TorrentFilter torrentFilter {filter, idSet, category, tag, isPrivate};
|
||||
QVariantList torrentList;
|
||||
for (const BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||
{
|
||||
|
@ -435,6 +440,8 @@ void TorrentsController::propertiesAction()
|
|||
const int uploadLimit = torrent->uploadLimit();
|
||||
const qreal ratio = torrent->realRatio();
|
||||
const qreal popularity = torrent->popularity();
|
||||
const bool hasMetadata = torrent->hasMetadata();
|
||||
const bool isPrivate = torrent->isPrivate();
|
||||
|
||||
const QJsonObject ret
|
||||
{
|
||||
|
@ -470,14 +477,16 @@ void TorrentsController::propertiesAction()
|
|||
{KEY_PROP_PIECE_SIZE, torrent->pieceLength()},
|
||||
{KEY_PROP_PIECES_HAVE, torrent->piecesHave()},
|
||||
{KEY_PROP_CREATED_BY, torrent->creator()},
|
||||
{KEY_PROP_ISPRIVATE, torrent->isPrivate()},
|
||||
{KEY_PROP_IS_PRIVATE, torrent->isPrivate()}, // used for maintaining backward compatibility
|
||||
{KEY_PROP_PRIVATE, (hasMetadata ? isPrivate : QJsonValue())},
|
||||
{KEY_PROP_ADDITION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->addedTime())},
|
||||
{KEY_PROP_LAST_SEEN, Utils::DateTime::toSecsSinceEpoch(torrent->lastSeenComplete())},
|
||||
{KEY_PROP_COMPLETION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->completedTime())},
|
||||
{KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->creationDate())},
|
||||
{KEY_PROP_SAVE_PATH, torrent->savePath().toString()},
|
||||
{KEY_PROP_DOWNLOAD_PATH, torrent->downloadPath().toString()},
|
||||
{KEY_PROP_COMMENT, torrent->comment()}
|
||||
{KEY_PROP_COMMENT, torrent->comment()},
|
||||
{KEY_PROP_HAS_METADATA, torrent->hasMetadata()}
|
||||
};
|
||||
|
||||
setResult(ret);
|
||||
|
@ -1092,11 +1101,11 @@ void TorrentsController::deleteAction()
|
|||
requireParams({u"hashes"_s, u"deleteFiles"_s});
|
||||
|
||||
const QStringList hashes {params()[u"hashes"_s].split(u'|')};
|
||||
const DeleteOption deleteOption = parseBool(params()[u"deleteFiles"_s]).value_or(false)
|
||||
? DeleteTorrentAndFiles : DeleteTorrent;
|
||||
const BitTorrent::TorrentRemoveOption deleteOption = parseBool(params()[u"deleteFiles"_s]).value_or(false)
|
||||
? BitTorrent::TorrentRemoveOption::RemoveContent : BitTorrent::TorrentRemoveOption::KeepContent;
|
||||
applyToTorrents(hashes, [deleteOption](const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
BitTorrent::Session::instance()->deleteTorrent(torrent->id(), deleteOption);
|
||||
BitTorrent::Session::instance()->removeTorrent(torrent->id(), deleteOption);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -737,16 +737,15 @@ void WebApplication::sessionStart()
|
|||
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, syncController, &SyncController::updateFreeDiskSpace);
|
||||
m_currentSession->registerAPIController(u"sync"_s, syncController);
|
||||
|
||||
QNetworkCookie cookie {m_sessionCookieName.toLatin1(), m_currentSession->id().toUtf8()};
|
||||
QNetworkCookie cookie {m_sessionCookieName.toLatin1(), m_currentSession->id().toLatin1()};
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(m_isSecureCookieEnabled && m_isHttpsEnabled);
|
||||
cookie.setPath(u"/"_s);
|
||||
QByteArray cookieRawForm = cookie.toRawForm();
|
||||
if (m_isCSRFProtectionEnabled)
|
||||
cookieRawForm.append("; SameSite=Strict");
|
||||
cookie.setSameSitePolicy(QNetworkCookie::SameSite::Strict);
|
||||
else if (cookie.isSecure())
|
||||
cookieRawForm.append("; SameSite=None");
|
||||
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookieRawForm)});
|
||||
cookie.setSameSitePolicy(QNetworkCookie::SameSite::None);
|
||||
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookie.toRawForm())});
|
||||
}
|
||||
|
||||
void WebApplication::sessionEnd()
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
#include "base/utils/version.h"
|
||||
#include "api/isessionmanager.h"
|
||||
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 11, 0};
|
||||
inline const Utils::Version<3, 2> API_VERSION {2, 11, 2};
|
||||
|
||||
class QTimer;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Globals from 'globals';
|
||||
import Html from 'eslint-plugin-html';
|
||||
import Js from '@eslint/js';
|
||||
import Stylistic from '@stylistic/eslint-plugin';
|
||||
import * as RegexpPlugin from 'eslint-plugin-regexp';
|
||||
import Globals from "globals";
|
||||
import Html from "eslint-plugin-html";
|
||||
import Js from "@eslint/js";
|
||||
import Stylistic from "@stylistic/eslint-plugin";
|
||||
import * as RegexpPlugin from "eslint-plugin-regexp";
|
||||
|
||||
export default [
|
||||
Js.configs.recommended,
|
||||
|
@ -26,9 +26,16 @@ export default [
|
|||
Stylistic
|
||||
},
|
||||
rules: {
|
||||
"curly": ["error", "multi-or-nest", "consistent"],
|
||||
"eqeqeq": "error",
|
||||
"guard-for-in": "error",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-var": "error",
|
||||
"operator-assignment": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"radix": "error",
|
||||
"Stylistic/no-mixed-operators": [
|
||||
"error",
|
||||
{
|
||||
|
@ -38,7 +45,16 @@ export default [
|
|||
}
|
||||
],
|
||||
"Stylistic/nonblock-statement-body-position": ["error", "below"],
|
||||
"Stylistic/semi": "error"
|
||||
"Stylistic/quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"Stylistic/semi": "error",
|
||||
"Stylistic/spaced-comment": ["error", "always", { "exceptions": ["*"] }]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"url": "https://github.com/qbittorrent/qBittorrent.git"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "js-beautify -r private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && prettier --write **.css",
|
||||
"lint": "eslint private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && stylelint **/*.css && html-validate private public"
|
||||
"format": "js-beautify -r *.mjs private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && prettier --write **.css",
|
||||
"lint": "eslint *.mjs private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && stylelint **/*.css && html-validate private public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@stylistic/eslint-plugin": "*",
|
||||
|
|
|
@ -8,42 +8,42 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
const hash = new URI().getData('hash');
|
||||
window.addEvent("domready", () => {
|
||||
const hash = new URI().getData("hash");
|
||||
if (!hash)
|
||||
return false;
|
||||
|
||||
$('peers').focus();
|
||||
$("peers").focus();
|
||||
|
||||
$('addPeersOk').addEvent('click', function(e) {
|
||||
$("addPeersOk").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
|
||||
const peers = $('peers').get('value').trim().split(/[\r\n]+/);
|
||||
const peers = $("peers").get("value").trim().split(/[\r\n]+/);
|
||||
if (peers.length === 0)
|
||||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/addPeers',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/addPeers",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hash,
|
||||
peers: peers.join('|')
|
||||
peers: peers.join("|")
|
||||
},
|
||||
onFailure: function() {
|
||||
alert("QBT_TR(Unable to add peers. Please ensure you are adhering to the IP:port format.)QBT_TR[CONTEXT=HttpServer]");
|
||||
|
|
|
@ -8,33 +8,33 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
window.addEvent("domready", () => {
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
$('trackersUrls').focus();
|
||||
$('addTrackersButton').addEvent('click', function(e) {
|
||||
$("trackersUrls").focus();
|
||||
$("addTrackersButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
const hash = new URI().getData('hash');
|
||||
const hash = new URI().getData("hash");
|
||||
new Request({
|
||||
url: 'api/v2/torrents/addTrackers',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/addTrackers",
|
||||
method: "post",
|
||||
data: {
|
||||
hash: hash,
|
||||
urls: $('trackersUrls').value
|
||||
urls: $("trackersUrls").value
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
|
|
|
@ -8,73 +8,73 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
function setRememberBtnEnabled(enable) {
|
||||
const btn = $('rememberBtn');
|
||||
const btn = $("rememberBtn");
|
||||
btn.disabled = !enable;
|
||||
|
||||
const icon = btn.getElementsByTagName('path')[0];
|
||||
const icon = btn.getElementsByTagName("path")[0];
|
||||
if (enable)
|
||||
icon.style.removeProperty('fill');
|
||||
icon.style.removeProperty("fill");
|
||||
else
|
||||
icon.style.fill = "var(--color-border-default)";
|
||||
}
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
window.addEvent("domready", () => {
|
||||
new Request({
|
||||
url: 'images/object-locked.svg',
|
||||
method: 'get',
|
||||
url: "images/object-locked.svg",
|
||||
method: "get",
|
||||
onSuccess: function(text, xml) {
|
||||
const newIcon = xml.childNodes[0];
|
||||
newIcon.style.height = '24px';
|
||||
newIcon.style.width = '24px';
|
||||
$('rememberBtn').appendChild(newIcon);
|
||||
newIcon.style.height = "24px";
|
||||
newIcon.style.width = "24px";
|
||||
$("rememberBtn").appendChild(newIcon);
|
||||
setRememberBtnEnabled(false);
|
||||
}
|
||||
}).send();
|
||||
|
||||
const isDeletingFiles = (new URI().getData('deleteFiles') === "true");
|
||||
$('deleteFromDiskCB').checked = isDeletingFiles;
|
||||
const isDeletingFiles = (new URI().getData("deleteFiles") === "true");
|
||||
$("deleteFromDiskCB").checked = isDeletingFiles;
|
||||
|
||||
const prefCache = window.parent.qBittorrent.Cache.preferences.get();
|
||||
let prefDeleteContentFiles = prefCache.delete_torrent_content_files;
|
||||
|
||||
$('deleteFromDiskCB').checked ||= prefDeleteContentFiles;
|
||||
$('deleteFromDiskCB').addEvent('click', function(e) {
|
||||
setRememberBtnEnabled($('deleteFromDiskCB').checked !== prefDeleteContentFiles);
|
||||
$("deleteFromDiskCB").checked ||= prefDeleteContentFiles;
|
||||
$("deleteFromDiskCB").addEvent("click", (e) => {
|
||||
setRememberBtnEnabled($("deleteFromDiskCB").checked !== prefDeleteContentFiles);
|
||||
});
|
||||
|
||||
// Set current "Delete files" choice as the default
|
||||
$('rememberBtn').addEvent('click', function(e) {
|
||||
$("rememberBtn").addEvent("click", (e) => {
|
||||
window.parent.qBittorrent.Cache.preferences.set({
|
||||
data: {
|
||||
'delete_torrent_content_files': $('deleteFromDiskCB').checked
|
||||
"delete_torrent_content_files": $("deleteFromDiskCB").checked
|
||||
},
|
||||
onSuccess: function() {
|
||||
prefDeleteContentFiles = $('deleteFromDiskCB').checked;
|
||||
prefDeleteContentFiles = $("deleteFromDiskCB").checked;
|
||||
setRememberBtnEnabled(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hashes = new URI().getData('hashes').split('|');
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', function(e) {
|
||||
const hashes = new URI().getData("hashes").split("|");
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
});
|
||||
$('confirmBtn').addEvent('click', function(e) {
|
||||
$("confirmBtn").addEvent("click", (e) => {
|
||||
parent.torrentsTable.deselectAll();
|
||||
new Event(e).stop();
|
||||
const cmd = 'api/v2/torrents/delete';
|
||||
const deleteFiles = $('deleteFromDiskCB').get('checked');
|
||||
const cmd = "api/v2/torrents/delete";
|
||||
const deleteFiles = $("deleteFromDiskCB").get("checked");
|
||||
new Request({
|
||||
url: cmd,
|
||||
method: 'post',
|
||||
method: "post",
|
||||
data: {
|
||||
'hashes': hashes.join('|'),
|
||||
'deleteFiles': deleteFiles
|
||||
"hashes": hashes.join("|"),
|
||||
"deleteFiles": deleteFiles
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
|
@ -91,7 +91,7 @@
|
|||
<p> QBT_TR(Are you sure you want to remove the selected torrents from the transfer list?)QBT_TR[CONTEXT=HttpServer]</p>
|
||||
<button id="rememberBtn" type="button" title="Remember choice" style="vertical-align: middle; padding: 4px 6px;">
|
||||
</button>
|
||||
<input type="checkbox" id="deleteFromDiskCB" /> <label for="deleteFromDiskCB"><i>QBT_TR(Also permanently delete the files)QBT_TR[CONTEXT=confirmDeletionDlg]</i></label><br /><br />
|
||||
<input type="checkbox" id="deleteFromDiskCB" /> <label for="deleteFromDiskCB"><i>QBT_TR(Also remove the content files)QBT_TR[CONTEXT=confirmDeletionDlg]</i></label><br /><br />
|
||||
<div style="text-align: right;">
|
||||
<input type="button" id="cancelBtn" value="QBT_TR(Cancel)QBT_TR[CONTEXT=MainWindow]" /> <input type="button" id="confirmBtn" value="QBT_TR(Remove)QBT_TR[CONTEXT=MainWindow]" />
|
||||
</div>
|
||||
|
|
|
@ -8,22 +8,22 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const paths = new URI().getData('paths').split('|');
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
window.addEvent("domready", () => {
|
||||
const paths = new URI().getData("paths").split("|");
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
$("confirmBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
paths.forEach((path) => {
|
||||
new Request({
|
||||
url: 'api/v2/rss/removeItem',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/removeItem",
|
||||
method: "post",
|
||||
data: {
|
||||
path: decodeURIComponent(path)
|
||||
},
|
||||
|
|
|
@ -8,25 +8,25 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const rules = new URI().getData('rules').split('|');
|
||||
window.addEvent("domready", () => {
|
||||
const rules = new URI().getData("rules").split("|");
|
||||
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('clearRulesPage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("clearRulesPage"));
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
$("confirmBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
rules.forEach((rule) => {
|
||||
window.parent.qBittorrent.RssDownloader.modifyRuleState(decodeURIComponent(rule), 'previouslyMatchedEpisodes', [], () => {
|
||||
window.parent.qBittorrent.RssDownloader.modifyRuleState(decodeURIComponent(rule), "previouslyMatchedEpisodes", [], () => {
|
||||
++completionCount;
|
||||
if (completionCount === rules.length) {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('clearRulesPage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("clearRulesPage"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,23 +8,23 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
window.addEvent('domready', () => {
|
||||
const rules = new URI().getData('rules').split('|');
|
||||
window.addEvent("domready", () => {
|
||||
const rules = new URI().getData("rules").split("|");
|
||||
|
||||
$('cancelBtn').focus();
|
||||
$('cancelBtn').addEvent('click', (e) => {
|
||||
$("cancelBtn").focus();
|
||||
$("cancelBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('removeRulePage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("removeRulePage"));
|
||||
});
|
||||
$('confirmBtn').addEvent('click', (e) => {
|
||||
$("confirmBtn").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
let completionCount = 0;
|
||||
rules.forEach((rule) => {
|
||||
new Request({
|
||||
url: 'api/v2/rss/removeRule',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/removeRule",
|
||||
method: "post",
|
||||
data: {
|
||||
ruleName: decodeURIComponent(rule)
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
++completionCount;
|
||||
if (completionCount === rules.length) {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('removeRulePage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("removeRulePage"));
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
|
|
|
@ -163,31 +163,31 @@
|
|||
<div id="download_spinner" class="mochaSpinner"></div>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const encodedUrls = new URI().getData('urls');
|
||||
const encodedUrls = new URI().getData("urls");
|
||||
if (encodedUrls) {
|
||||
const urls = encodedUrls.split('|').map(function(url) {
|
||||
const urls = encodedUrls.split("|").map((url) => {
|
||||
return decodeURIComponent(url);
|
||||
});
|
||||
|
||||
if (urls.length)
|
||||
$('urls').set('value', urls.join("\n"));
|
||||
$("urls").set("value", urls.join("\n"));
|
||||
}
|
||||
|
||||
let submitted = false;
|
||||
|
||||
$('downloadForm').addEventListener("submit", function() {
|
||||
$('startTorrentHidden').value = $('startTorrent').checked ? 'false' : 'true';
|
||||
$("downloadForm").addEventListener("submit", () => {
|
||||
$("startTorrentHidden").value = $("startTorrent").checked ? "false" : "true";
|
||||
|
||||
$('dlLimitHidden').value = $('dlLimitText').value.toInt() * 1024;
|
||||
$('upLimitHidden').value = $('upLimitText').value.toInt() * 1024;
|
||||
$("dlLimitHidden").value = $("dlLimitText").value.toInt() * 1024;
|
||||
$("upLimitHidden").value = $("upLimitText").value.toInt() * 1024;
|
||||
|
||||
$('download_spinner').style.display = "block";
|
||||
$("download_spinner").style.display = "block";
|
||||
submitted = true;
|
||||
});
|
||||
|
||||
$('download_frame').addEventListener("load", function() {
|
||||
$("download_frame").addEventListener("load", () => {
|
||||
if (submitted)
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
});
|
||||
|
|
|
@ -25,17 +25,17 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const hashes = new URI().getData('hashes').split('|');
|
||||
const hashes = new URI().getData("hashes").split("|");
|
||||
const setDlLimit = function() {
|
||||
const limit = $("dllimitUpdatevalue").value.toInt() * 1024;
|
||||
if (hashes[0] === "global") {
|
||||
new Request({
|
||||
url: 'api/v2/transfer/setDownloadLimit',
|
||||
method: 'post',
|
||||
url: "api/v2/transfer/setDownloadLimit",
|
||||
method: "post",
|
||||
data: {
|
||||
'limit': limit
|
||||
"limit": limit
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.updateMainData();
|
||||
|
@ -45,11 +45,11 @@
|
|||
}
|
||||
else {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setDownloadLimit',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setDownloadLimit",
|
||||
method: "post",
|
||||
data: {
|
||||
'hashes': hashes.join('|'),
|
||||
'limit': limit
|
||||
"hashes": hashes.join("|"),
|
||||
"limit": limit
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
|
@ -59,24 +59,24 @@
|
|||
};
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('applyButton').click();
|
||||
"Enter": function(event) {
|
||||
$("applyButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
$('dllimitUpdatevalue').focus();
|
||||
$("dllimitUpdatevalue").focus();
|
||||
|
||||
MochaUI.addDlLimitSlider(hashes);
|
||||
</script>
|
||||
|
|
|
@ -8,44 +8,44 @@
|
|||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
window.addEvent("domready", () => {
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('editTrackerButton').click();
|
||||
"Enter": function(event) {
|
||||
$("editTrackerButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
const currentUrl = new URI().getData('url');
|
||||
const currentUrl = new URI().getData("url");
|
||||
if (!currentUrl)
|
||||
return false;
|
||||
|
||||
$('trackerUrl').value = currentUrl;
|
||||
$('trackerUrl').focus();
|
||||
$("trackerUrl").value = currentUrl;
|
||||
$("trackerUrl").focus();
|
||||
|
||||
$('editTrackerButton').addEvent('click', function(e) {
|
||||
$("editTrackerButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
const hash = new URI().getData('hash');
|
||||
const hash = new URI().getData("hash");
|
||||
new Request({
|
||||
url: 'api/v2/torrents/editTracker',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/editTracker",
|
||||
method: "post",
|
||||
data: {
|
||||
hash: hash,
|
||||
origUrl: currentUrl,
|
||||
newUrl: $('trackerUrl').value
|
||||
newUrl: $("trackerUrl").value
|
||||
},
|
||||
onComplete: function() {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
|
|
|
@ -9,54 +9,54 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('categoryNameButton').click();
|
||||
"Enter": function(event) {
|
||||
$("categoryNameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action'));
|
||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes'));
|
||||
const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData('categoryName'));
|
||||
const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData('savePath'));
|
||||
window.addEvent("domready", () => {
|
||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData("action"));
|
||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData("hashes"));
|
||||
const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData("categoryName"));
|
||||
const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData("savePath"));
|
||||
|
||||
if (uriAction === "edit") {
|
||||
if (!uriCategoryName)
|
||||
return false;
|
||||
|
||||
$('categoryName').set('disabled', true);
|
||||
$('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||
$('savePath').set('value', window.qBittorrent.Misc.escapeHtml(uriSavePath));
|
||||
$('savePath').focus();
|
||||
$("categoryName").set("disabled", true);
|
||||
$("categoryName").set("value", window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||
$("savePath").set("value", window.qBittorrent.Misc.escapeHtml(uriSavePath));
|
||||
$("savePath").focus();
|
||||
}
|
||||
else if (uriAction === "createSubcategory") {
|
||||
$('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||
$('categoryName').focus();
|
||||
$("categoryName").set("value", window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||
$("categoryName").focus();
|
||||
}
|
||||
else {
|
||||
$('categoryName').focus();
|
||||
$("categoryName").focus();
|
||||
}
|
||||
|
||||
$('categoryNameButton').addEvent('click', function(e) {
|
||||
$("categoryNameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
|
||||
const savePath = $('savePath').value.trim();
|
||||
const categoryName = $('categoryName').value.trim();
|
||||
const savePath = $("savePath").value.trim();
|
||||
const categoryName = $("categoryName").value.trim();
|
||||
|
||||
const verifyCategoryName = function(name) {
|
||||
if ((name === null) || (name === ""))
|
||||
|
@ -74,16 +74,16 @@
|
|||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/createCategory",
|
||||
method: "post",
|
||||
data: {
|
||||
category: categoryName,
|
||||
savePath: savePath
|
||||
},
|
||||
onSuccess: function() {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setCategory",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: uriHashes,
|
||||
category: categoryName
|
||||
|
@ -104,8 +104,8 @@
|
|||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createCategory',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/createCategory",
|
||||
method: "post",
|
||||
data: {
|
||||
category: categoryName,
|
||||
savePath: savePath
|
||||
|
@ -117,8 +117,8 @@
|
|||
break;
|
||||
case "edit":
|
||||
new Request({
|
||||
url: 'api/v2/torrents/editCategory',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/editCategory",
|
||||
method: "post",
|
||||
data: {
|
||||
category: uriCategoryName, // category name can't be changed
|
||||
savePath: savePath
|
||||
|
|
|
@ -9,45 +9,45 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
"Enter": (event) => {
|
||||
$("submitButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
"Escape": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
"Esc": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('feedURL').focus();
|
||||
const path = new URI().getData('path');
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
window.addEvent("domready", () => {
|
||||
$("feedURL").focus();
|
||||
const path = new URI().getData("path");
|
||||
$("submitButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const feedURL = $('feedURL').value.trim();
|
||||
if (feedURL === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const feedURL = $("feedURL").value.trim();
|
||||
if (feedURL === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$('submitButton').disabled = true;
|
||||
$("submitButton").disabled = true;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/addFeed',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/addFeed",
|
||||
method: "post",
|
||||
data: {
|
||||
url: feedURL,
|
||||
path: path ? (path + '\\' + feedURL) : ''
|
||||
path: path ? (path + "\\" + feedURL) : ""
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
|
@ -56,7 +56,7 @@
|
|||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
$('submitButton').disabled = false;
|
||||
$("submitButton").disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -9,44 +9,44 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
"Enter": (event) => {
|
||||
$("submitButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
"Escape": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
"Esc": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('folderName').focus();
|
||||
const path = new URI().getData('path');
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
window.addEvent("domready", () => {
|
||||
$("folderName").focus();
|
||||
const path = new URI().getData("path");
|
||||
$("submitButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const folderName = $('folderName').value.trim();
|
||||
if (folderName === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const folderName = $("folderName").value.trim();
|
||||
if (folderName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$('submitButton').disabled = true;
|
||||
$("submitButton").disabled = true;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/addFolder',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/addFolder",
|
||||
method: "post",
|
||||
data: {
|
||||
path: path ? (path + '\\' + folderName) : folderName
|
||||
path: path ? (path + "\\" + folderName) : folderName
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||
|
@ -55,7 +55,7 @@
|
|||
onFailure: (response) => {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
$('submitButton').disabled = false;
|
||||
$("submitButton").disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -9,46 +9,46 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('submitButton').click();
|
||||
"Enter": (event) => {
|
||||
$("submitButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
"Escape": (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
"Esc": (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
$('name').focus();
|
||||
$('submitButton').addEvent('click', (e) => {
|
||||
window.addEvent("domready", () => {
|
||||
$("name").focus();
|
||||
$("submitButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const name = $('name').value.trim();
|
||||
if (name === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const name = $("name").value.trim();
|
||||
if (name === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
$('submitButton').disabled = true;
|
||||
$("submitButton").disabled = true;
|
||||
new Request({
|
||||
url: 'api/v2/rss/setRule',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/setRule",
|
||||
method: "post",
|
||||
data: {
|
||||
ruleName: name,
|
||||
ruleDef: '{}'
|
||||
ruleDef: "{}"
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -9,39 +9,39 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('tagNameButton').click();
|
||||
"Enter": function(event) {
|
||||
$("tagNameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action'));
|
||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes'));
|
||||
window.addEvent("domready", () => {
|
||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData("action"));
|
||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData("hashes"));
|
||||
|
||||
if (uriAction === 'create')
|
||||
$('legendText').innerText = 'QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]';
|
||||
if (uriAction === "create")
|
||||
$("legendText").innerText = "QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]";
|
||||
|
||||
$('tagName').focus();
|
||||
$("tagName").focus();
|
||||
|
||||
$('tagNameButton').addEvent('click', function(e) {
|
||||
$("tagNameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
|
||||
const tagName = $('tagName').value.trim();
|
||||
const tagName = $("tagName").value.trim();
|
||||
|
||||
const verifyTagName = function(name) {
|
||||
if ((name === null) || (name === ""))
|
||||
|
@ -59,8 +59,8 @@
|
|||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/addTags',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/addTags",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: uriHashes,
|
||||
tags: tagName,
|
||||
|
@ -76,8 +76,8 @@
|
|||
return;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/createTags',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/createTags",
|
||||
method: "post",
|
||||
data: {
|
||||
tags: tagName,
|
||||
},
|
||||
|
|
|
@ -9,45 +9,45 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('renameButton').click();
|
||||
"Enter": function(event) {
|
||||
$("renameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
const name = new URI().getData('name');
|
||||
window.addEvent("domready", () => {
|
||||
const name = new URI().getData("name");
|
||||
// set text field to current value
|
||||
if (name)
|
||||
$('rename').value = name;
|
||||
$("rename").value = name;
|
||||
|
||||
$('rename').focus();
|
||||
$('renameButton').addEvent('click', function(e) {
|
||||
$("rename").focus();
|
||||
$("renameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const name = $('rename').value.trim();
|
||||
const name = $("rename").value.trim();
|
||||
if ((name === null) || (name === ""))
|
||||
return false;
|
||||
|
||||
const hash = new URI().getData('hash');
|
||||
const hash = new URI().getData("hash");
|
||||
if (hash) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/rename',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/rename",
|
||||
method: "post",
|
||||
data: {
|
||||
hash: hash,
|
||||
name: name
|
||||
|
|
|
@ -9,51 +9,51 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('renameButton').click();
|
||||
"Enter": (event) => {
|
||||
$("renameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
"Escape": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
"Esc": (event) => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
const oldPath = new URI().getData('oldPath');
|
||||
window.addEvent("domready", () => {
|
||||
const oldPath = new URI().getData("oldPath");
|
||||
|
||||
$('rename').value = oldPath;
|
||||
$('rename').focus();
|
||||
$('rename').setSelectionRange(0, oldPath.length);
|
||||
$("rename").value = oldPath;
|
||||
$("rename").focus();
|
||||
$("rename").setSelectionRange(0, oldPath.length);
|
||||
|
||||
$('renameButton').addEvent('click', (e) => {
|
||||
$("renameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const newPath = $('rename').value.trim();
|
||||
if (newPath === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const newPath = $("rename").value.trim();
|
||||
if (newPath === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPath === oldPath) {
|
||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
||||
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$('renameButton').disabled = true;
|
||||
$("renameButton").disabled = true;
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/rss/moveItem',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/moveItem",
|
||||
method: "post",
|
||||
data: {
|
||||
itemPath: oldPath,
|
||||
destPath: newPath
|
||||
|
@ -63,10 +63,9 @@
|
|||
window.parent.qBittorrent.Client.closeWindows();
|
||||
},
|
||||
onFailure: (response) => {
|
||||
if (response.status === 409) {
|
||||
if (response.status === 409)
|
||||
alert(response.responseText);
|
||||
}
|
||||
$('renameButton').disabled = false;
|
||||
$("renameButton").disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -10,60 +10,60 @@
|
|||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': function(event) {
|
||||
$('renameButton').click();
|
||||
"Enter": function(event) {
|
||||
$("renameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
|
||||
window.addEvent('domready', function() {
|
||||
const hash = new URI().getData('hash');
|
||||
const oldPath = new URI().getData('path');
|
||||
const isFolder = ((new URI().getData('isFolder')) === 'true');
|
||||
window.addEvent("domready", () => {
|
||||
const hash = new URI().getData("hash");
|
||||
const oldPath = new URI().getData("path");
|
||||
const isFolder = ((new URI().getData("isFolder")) === "true");
|
||||
|
||||
const oldName = window.qBittorrent.Filesystem.fileName(oldPath);
|
||||
$('rename').value = oldName;
|
||||
$('rename').focus();
|
||||
$("rename").value = oldName;
|
||||
$("rename").focus();
|
||||
if (!isFolder)
|
||||
$('rename').setSelectionRange(0, oldName.lastIndexOf('.'));
|
||||
$("rename").setSelectionRange(0, oldName.lastIndexOf("."));
|
||||
|
||||
$('renameButton').addEvent('click', function(e) {
|
||||
$("renameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const newName = $('rename').value.trim();
|
||||
if (newName === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const newName = $("rename").value.trim();
|
||||
if (newName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName === oldName) {
|
||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
||||
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$('renameButton').disabled = true;
|
||||
$("renameButton").disabled = true;
|
||||
|
||||
const parentPath = window.qBittorrent.Filesystem.folderName(oldPath);
|
||||
const newPath = parentPath
|
||||
? parentPath + window.qBittorrent.Filesystem.PathSeparator + newName
|
||||
: newName;
|
||||
new Request({
|
||||
url: isFolder ? 'api/v2/torrents/renameFolder' : 'api/v2/torrents/renameFile',
|
||||
method: 'post',
|
||||
url: isFolder ? "api/v2/torrents/renameFolder" : "api/v2/torrents/renameFile",
|
||||
method: "post",
|
||||
data: {
|
||||
hash: hash,
|
||||
oldPath: oldPath,
|
||||
|
@ -73,8 +73,8 @@
|
|||
window.parent.qBittorrent.Client.closeWindows();
|
||||
},
|
||||
onFailure: function() {
|
||||
alert('QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]');
|
||||
$('renameButton').disabled = false;
|
||||
alert("QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]");
|
||||
$("renameButton").disabled = false;
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -12,23 +12,22 @@
|
|||
<script src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script src="scripts/rename-files.js?v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.parent.qBittorrent !== undefined) {
|
||||
if (window.parent.qBittorrent !== undefined)
|
||||
window.qBittorrent = window.parent.qBittorrent;
|
||||
}
|
||||
window.qBittorrent = window.parent.qBittorrent;
|
||||
|
||||
var TriState = window.qBittorrent.FileTree.TriState;
|
||||
var data = window.MUI.Windows.instances['multiRenamePage'].options.data;
|
||||
var bulkRenameFilesContextMenu;
|
||||
const TriState = window.qBittorrent.FileTree.TriState;
|
||||
const data = window.MUI.Windows.instances["multiRenamePage"].options.data;
|
||||
let bulkRenameFilesContextMenu;
|
||||
if (!bulkRenameFilesContextMenu) {
|
||||
bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||
targets: '#bulkRenameFilesTableDiv tr',
|
||||
menu: 'multiRenameFilesMenu',
|
||||
targets: "#bulkRenameFilesTableDiv tr",
|
||||
menu: "multiRenameFilesMenu",
|
||||
actions: {
|
||||
ToggleSelection: function(element, ref) {
|
||||
const rowId = parseInt(element.get('data-row-id'));
|
||||
const rowId = parseInt(element.get("data-row-id"), 10);
|
||||
const row = bulkRenameFilesTable.getNode(rowId);
|
||||
const checkState = (row.checked === 1) ? 0 : 1;
|
||||
bulkRenameFilesTable.toggleNodeTreeCheckbox(rowId, checkState);
|
||||
|
@ -44,20 +43,19 @@
|
|||
}
|
||||
|
||||
// Setup the dynamic table for bulk renaming
|
||||
var bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
||||
bulkRenameFilesTable.setup('bulkRenameFilesTableDiv', 'bulkRenameFilesTableFixedHeaderDiv', bulkRenameFilesContextMenu);
|
||||
const bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
||||
bulkRenameFilesTable.setup("bulkRenameFilesTableDiv", "bulkRenameFilesTableFixedHeaderDiv", bulkRenameFilesContextMenu);
|
||||
|
||||
// Inject checkbox into the first column of the table header
|
||||
var tableHeaders = $$('#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th');
|
||||
var checkboxHeader;
|
||||
const tableHeaders = $$("#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||
let checkboxHeader;
|
||||
if (tableHeaders.length > 0) {
|
||||
if (checkboxHeader) {
|
||||
if (checkboxHeader)
|
||||
checkboxHeader.remove();
|
||||
}
|
||||
checkboxHeader = new Element('input');
|
||||
checkboxHeader.set('type', 'checkbox');
|
||||
checkboxHeader.set('id', 'rootMultiRename_cb');
|
||||
checkboxHeader.addEvent('click', function(e) {
|
||||
checkboxHeader = new Element("input");
|
||||
checkboxHeader.set("type", "checkbox");
|
||||
checkboxHeader.set("id", "rootMultiRename_cb");
|
||||
checkboxHeader.addEvent("click", (e) => {
|
||||
bulkRenameFilesTable.toggleGlobalCheckbox();
|
||||
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
||||
fileRenamer.update();
|
||||
|
@ -69,16 +67,16 @@
|
|||
|
||||
// Register keyboard events to modal window
|
||||
// https://github.com/qbittorrent/qBittorrent/pull/18687#discussion_r1135045726
|
||||
var keyboard;
|
||||
let keyboard;
|
||||
if (!keyboard) {
|
||||
keyboard = new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Escape': function(event) {
|
||||
"Escape": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': function(event) {
|
||||
"Esc": function(event) {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
@ -87,55 +85,55 @@
|
|||
keyboard.activate();
|
||||
}
|
||||
|
||||
var fileRenamer = new window.qBittorrent.MultiRename.RenameFiles();
|
||||
const fileRenamer = new window.qBittorrent.MultiRename.RenameFiles();
|
||||
fileRenamer.hash = data.hash;
|
||||
|
||||
// Load Multi Rename Preferences
|
||||
var multiRenamePrefChecked = LocalPreferences.get('multirename_rememberPreferences', "true") === "true";
|
||||
$('multirename_rememberprefs_checkbox').setProperty('checked', multiRenamePrefChecked);
|
||||
const multiRenamePrefChecked = LocalPreferences.get("multirename_rememberPreferences", "true") === "true";
|
||||
$("multirename_rememberprefs_checkbox").setProperty("checked", multiRenamePrefChecked);
|
||||
|
||||
if (multiRenamePrefChecked) {
|
||||
var multirename_search = LocalPreferences.get('multirename_search', '');
|
||||
const multirename_search = LocalPreferences.get("multirename_search", "");
|
||||
fileRenamer.setSearch(multirename_search);
|
||||
$('multiRenameSearch').set('value', multirename_search);
|
||||
$("multiRenameSearch").set("value", multirename_search);
|
||||
|
||||
var multirename_useRegex = LocalPreferences.get('multirename_useRegex', false);
|
||||
fileRenamer.useRegex = multirename_useRegex === 'true';
|
||||
$('use_regex_search').checked = fileRenamer.useRegex;
|
||||
const multirename_useRegex = LocalPreferences.get("multirename_useRegex", false);
|
||||
fileRenamer.useRegex = multirename_useRegex === "true";
|
||||
$("use_regex_search").checked = fileRenamer.useRegex;
|
||||
|
||||
var multirename_matchAllOccurrences = LocalPreferences.get('multirename_matchAllOccurrences', false);
|
||||
fileRenamer.matchAllOccurrences = multirename_matchAllOccurrences === 'true';
|
||||
$('match_all_occurrences').checked = fileRenamer.matchAllOccurrences;
|
||||
const multirename_matchAllOccurrences = LocalPreferences.get("multirename_matchAllOccurrences", false);
|
||||
fileRenamer.matchAllOccurrences = multirename_matchAllOccurrences === "true";
|
||||
$("match_all_occurrences").checked = fileRenamer.matchAllOccurrences;
|
||||
|
||||
var multirename_caseSensitive = LocalPreferences.get('multirename_caseSensitive', false);
|
||||
fileRenamer.caseSensitive = multirename_caseSensitive === 'true';
|
||||
$('case_sensitive').checked = fileRenamer.caseSensitive;
|
||||
const multirename_caseSensitive = LocalPreferences.get("multirename_caseSensitive", false);
|
||||
fileRenamer.caseSensitive = multirename_caseSensitive === "true";
|
||||
$("case_sensitive").checked = fileRenamer.caseSensitive;
|
||||
|
||||
var multirename_replace = LocalPreferences.get('multirename_replace', '');
|
||||
const multirename_replace = LocalPreferences.get("multirename_replace", "");
|
||||
fileRenamer.setReplacement(multirename_replace);
|
||||
$('multiRenameReplace').set('value', multirename_replace);
|
||||
$("multiRenameReplace").set("value", multirename_replace);
|
||||
|
||||
var multirename_appliesTo = LocalPreferences.get('multirename_appliesTo', window.qBittorrent.MultiRename.AppliesTo.FilenameExtension);
|
||||
const multirename_appliesTo = LocalPreferences.get("multirename_appliesTo", window.qBittorrent.MultiRename.AppliesTo.FilenameExtension);
|
||||
fileRenamer.appliesTo = window.qBittorrent.MultiRename.AppliesTo[multirename_appliesTo];
|
||||
$('applies_to_option').set('value', fileRenamer.appliesTo);
|
||||
$("applies_to_option").set("value", fileRenamer.appliesTo);
|
||||
|
||||
var multirename_includeFiles = LocalPreferences.get('multirename_includeFiles', true);
|
||||
fileRenamer.includeFiles = multirename_includeFiles === 'true';
|
||||
$('include_files').checked = fileRenamer.includeFiles;
|
||||
const multirename_includeFiles = LocalPreferences.get("multirename_includeFiles", true);
|
||||
fileRenamer.includeFiles = multirename_includeFiles === "true";
|
||||
$("include_files").checked = fileRenamer.includeFiles;
|
||||
|
||||
var multirename_includeFolders = LocalPreferences.get('multirename_includeFolders', false);
|
||||
fileRenamer.includeFolders = multirename_includeFolders === 'true';
|
||||
$('include_folders').checked = fileRenamer.includeFolders;
|
||||
const multirename_includeFolders = LocalPreferences.get("multirename_includeFolders", false);
|
||||
fileRenamer.includeFolders = multirename_includeFolders === "true";
|
||||
$("include_folders").checked = fileRenamer.includeFolders;
|
||||
|
||||
var multirename_fileEnumerationStart = LocalPreferences.get('multirename_fileEnumerationStart', 0);
|
||||
fileRenamer.fileEnumerationStart = parseInt(multirename_fileEnumerationStart);
|
||||
$('file_counter').set('value', fileRenamer.fileEnumerationStart);
|
||||
const multirename_fileEnumerationStart = LocalPreferences.get("multirename_fileEnumerationStart", 0);
|
||||
fileRenamer.fileEnumerationStart = parseInt(multirename_fileEnumerationStart, 10);
|
||||
$("file_counter").set("value", fileRenamer.fileEnumerationStart);
|
||||
|
||||
var multirename_replaceAll = LocalPreferences.get('multirename_replaceAll', false);
|
||||
fileRenamer.replaceAll = multirename_replaceAll === 'true';
|
||||
var renameButtonValue = fileRenamer.replaceAll ? 'Replace All' : 'Replace';
|
||||
$('renameOptions').set('value', renameButtonValue);
|
||||
$('renameButton').set('value', renameButtonValue);
|
||||
const multirename_replaceAll = LocalPreferences.get("multirename_replaceAll", false);
|
||||
fileRenamer.replaceAll = multirename_replaceAll === "true";
|
||||
const renameButtonValue = fileRenamer.replaceAll ? "Replace All" : "Replace";
|
||||
$("renameOptions").set("value", renameButtonValue);
|
||||
$("renameButton").set("value", renameButtonValue);
|
||||
}
|
||||
|
||||
// Fires every time a row's selection changes
|
||||
|
@ -145,28 +143,28 @@
|
|||
};
|
||||
|
||||
// Setup Search Events that control renaming
|
||||
$('multiRenameSearch').addEvent('input', function(e) {
|
||||
let sanitized = e.target.value.replace(/\n/g, '');
|
||||
$('multiRenameSearch').set('value', sanitized);
|
||||
$("multiRenameSearch").addEvent("input", (e) => {
|
||||
const sanitized = e.target.value.replace(/\n/g, "");
|
||||
$("multiRenameSearch").set("value", sanitized);
|
||||
|
||||
// Search input has changed
|
||||
$('multiRenameSearch').style['border-color'] = '';
|
||||
LocalPreferences.set('multirename_search', sanitized);
|
||||
$("multiRenameSearch").style["border-color"] = "";
|
||||
LocalPreferences.set("multirename_search", sanitized);
|
||||
fileRenamer.setSearch(sanitized);
|
||||
});
|
||||
$('use_regex_search').addEvent('change', function(e) {
|
||||
$("use_regex_search").addEvent("change", (e) => {
|
||||
fileRenamer.useRegex = e.target.checked;
|
||||
LocalPreferences.set('multirename_useRegex', e.target.checked);
|
||||
LocalPreferences.set("multirename_useRegex", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$('match_all_occurrences').addEvent('change', function(e) {
|
||||
$("match_all_occurrences").addEvent("change", (e) => {
|
||||
fileRenamer.matchAllOccurrences = e.target.checked;
|
||||
LocalPreferences.set('multirename_matchAllOccurrences', e.target.checked);
|
||||
LocalPreferences.set("multirename_matchAllOccurrences", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$('case_sensitive').addEvent('change', function(e) {
|
||||
$("case_sensitive").addEvent("change", (e) => {
|
||||
fileRenamer.caseSensitive = e.target.checked;
|
||||
LocalPreferences.set('multirename_caseSensitive', e.target.checked);
|
||||
LocalPreferences.set("multirename_caseSensitive", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
|
||||
|
@ -177,138 +175,136 @@
|
|||
// Clear renamed column
|
||||
document
|
||||
.querySelectorAll("span[id^='filesTablefileRenamed']")
|
||||
.forEach(function(span) {
|
||||
span.set('text', "");
|
||||
.forEach((span) => {
|
||||
span.set("text", "");
|
||||
});
|
||||
|
||||
// Update renamed column for matched rows
|
||||
for (let i = 0; i < matchedRows.length; ++i) {
|
||||
const row = matchedRows[i];
|
||||
$('filesTablefileRenamed' + row.rowId).set('text', row.renamed);
|
||||
$("filesTablefileRenamed" + row.rowId).set("text", row.renamed);
|
||||
}
|
||||
};
|
||||
fileRenamer.onInvalidRegex = function(err) {
|
||||
$('multiRenameSearch').style['border-color'] = '#CC0033';
|
||||
$("multiRenameSearch").style["border-color"] = "#CC0033";
|
||||
};
|
||||
|
||||
// Setup Replace Events that control renaming
|
||||
$('multiRenameReplace').addEvent('input', function(e) {
|
||||
let sanitized = e.target.value.replace(/\n/g, '');
|
||||
$('multiRenameReplace').set('value', sanitized);
|
||||
$("multiRenameReplace").addEvent("input", (e) => {
|
||||
const sanitized = e.target.value.replace(/\n/g, "");
|
||||
$("multiRenameReplace").set("value", sanitized);
|
||||
|
||||
// Replace input has changed
|
||||
$('multiRenameReplace').style['border-color'] = '';
|
||||
LocalPreferences.set('multirename_replace', sanitized);
|
||||
$("multiRenameReplace").style["border-color"] = "";
|
||||
LocalPreferences.set("multirename_replace", sanitized);
|
||||
fileRenamer.setReplacement(sanitized);
|
||||
});
|
||||
$('applies_to_option').addEvent('change', function(e) {
|
||||
$("applies_to_option").addEvent("change", (e) => {
|
||||
fileRenamer.appliesTo = e.target.value;
|
||||
LocalPreferences.set('multirename_appliesTo', e.target.value);
|
||||
LocalPreferences.set("multirename_appliesTo", e.target.value);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$('include_files').addEvent('change', function(e) {
|
||||
$("include_files").addEvent("change", (e) => {
|
||||
fileRenamer.includeFiles = e.target.checked;
|
||||
LocalPreferences.set('multirename_includeFiles', e.target.checked);
|
||||
LocalPreferences.set("multirename_includeFiles", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$('include_folders').addEvent('change', function(e) {
|
||||
$("include_folders").addEvent("change", (e) => {
|
||||
fileRenamer.includeFolders = e.target.checked;
|
||||
LocalPreferences.set('multirename_includeFolders', e.target.checked);
|
||||
LocalPreferences.set("multirename_includeFolders", e.target.checked);
|
||||
fileRenamer.update();
|
||||
});
|
||||
$('file_counter').addEvent('input', function(e) {
|
||||
$("file_counter").addEvent("input", (e) => {
|
||||
let value = e.target.valueAsNumber;
|
||||
if (!value) { value = 0; }
|
||||
if (value < 0) { value = 0; }
|
||||
if (value > 99999999) { value = 99999999; }
|
||||
if (!value)
|
||||
value = 0;
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 99999999)
|
||||
value = 99999999;
|
||||
fileRenamer.fileEnumerationStart = value;
|
||||
$('file_counter').set('value', value);
|
||||
LocalPreferences.set('multirename_fileEnumerationStart', value);
|
||||
$("file_counter").set("value", value);
|
||||
LocalPreferences.set("multirename_fileEnumerationStart", value);
|
||||
fileRenamer.update();
|
||||
});
|
||||
|
||||
// Setup Rename Operation Events
|
||||
$('renameButton').addEvent('click', function(e) {
|
||||
$("renameButton").addEvent("click", (e) => {
|
||||
// Disable Search Options
|
||||
$('multiRenameSearch').disabled = true;
|
||||
$('use_regex_search').disabled = true;
|
||||
$('match_all_occurrences').disabled = true;
|
||||
$('case_sensitive').disabled = true;
|
||||
$("multiRenameSearch").disabled = true;
|
||||
$("use_regex_search").disabled = true;
|
||||
$("match_all_occurrences").disabled = true;
|
||||
$("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;
|
||||
$("multiRenameReplace").disabled = true;
|
||||
$("applies_to_option").disabled = true;
|
||||
$("include_files").disabled = true;
|
||||
$("include_folders").disabled = true;
|
||||
$("file_counter").disabled = true;
|
||||
// Disable Rename Buttons
|
||||
$('renameButton').disabled = true;
|
||||
$('renameOptions').disabled = true;
|
||||
$("renameButton").disabled = true;
|
||||
$("renameOptions").disabled = true;
|
||||
// Clear error text
|
||||
$('rename_error').set('text', '');
|
||||
$("rename_error").set("text", "");
|
||||
fileRenamer.rename();
|
||||
});
|
||||
fileRenamer.onRenamed = function(rows) {
|
||||
// Disable Search Options
|
||||
$('multiRenameSearch').disabled = false;
|
||||
$('use_regex_search').disabled = false;
|
||||
$('match_all_occurrences').disabled = false;
|
||||
$('case_sensitive').disabled = false;
|
||||
$("multiRenameSearch").disabled = false;
|
||||
$("use_regex_search").disabled = false;
|
||||
$("match_all_occurrences").disabled = false;
|
||||
$("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;
|
||||
$("multiRenameReplace").disabled = false;
|
||||
$("applies_to_option").disabled = false;
|
||||
$("include_files").disabled = false;
|
||||
$("include_folders").disabled = false;
|
||||
$("file_counter").disabled = false;
|
||||
// Disable Rename Buttons
|
||||
$('renameButton').disabled = false;
|
||||
$('renameOptions').disabled = false;
|
||||
$("renameButton").disabled = false;
|
||||
$("renameOptions").disabled = false;
|
||||
|
||||
// Recreate table
|
||||
let selectedRows = bulkRenameFilesTable.getSelectedRows().map(row => row.rowId.toString());
|
||||
for (let renamedRow of rows) {
|
||||
for (const renamedRow of rows)
|
||||
selectedRows = selectedRows.filter(selectedRow => selectedRow !== renamedRow.rowId.toString());
|
||||
}
|
||||
bulkRenameFilesTable.clear();
|
||||
|
||||
// Adjust file enumeration count by 1 when replacing single files to prevent naming conflicts
|
||||
if (!fileRenamer.replaceAll) {
|
||||
fileRenamer.fileEnumerationStart++;
|
||||
$('file_counter').set('value', fileRenamer.fileEnumerationStart);
|
||||
$("file_counter").set("value", fileRenamer.fileEnumerationStart);
|
||||
}
|
||||
setupTable(selectedRows);
|
||||
};
|
||||
fileRenamer.onRenameError = function(err, row) {
|
||||
if (err.xhr.status === 409) {
|
||||
$('rename_error').set('text', `QBT_TR(Rename failed: file or folder already exists)QBT_TR[CONTEXT=PropertiesWidget] \`${row.renamed}\``);
|
||||
}
|
||||
if (err.xhr.status === 409)
|
||||
$("rename_error").set("text", `QBT_TR(Rename failed: file or folder already exists)QBT_TR[CONTEXT=PropertiesWidget] \`${row.renamed}\``);
|
||||
};
|
||||
$('renameOptions').addEvent('change', function(e) {
|
||||
$("renameOptions").addEvent("change", (e) => {
|
||||
const combobox = e.target;
|
||||
const replaceOperation = combobox.value;
|
||||
if (replaceOperation === "Replace") {
|
||||
if (replaceOperation === "Replace")
|
||||
fileRenamer.replaceAll = false;
|
||||
}
|
||||
else if (replaceOperation === "Replace All") {
|
||||
else if (replaceOperation === "Replace All")
|
||||
fileRenamer.replaceAll = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
fileRenamer.replaceAll = false;
|
||||
}
|
||||
LocalPreferences.set('multirename_replaceAll', fileRenamer.replaceAll);
|
||||
$('renameButton').set('value', replaceOperation);
|
||||
LocalPreferences.set("multirename_replaceAll", fileRenamer.replaceAll);
|
||||
$("renameButton").set("value", replaceOperation);
|
||||
});
|
||||
$('closeButton').addEvent('click', function() {
|
||||
$("closeButton").addEvent("click", () => {
|
||||
window.parent.qBittorrent.Client.closeWindows();
|
||||
event.preventDefault();
|
||||
});
|
||||
// synchronize header scrolling to table body
|
||||
$('bulkRenameFilesTableDiv').onscroll = function() {
|
||||
$("bulkRenameFilesTableDiv").onscroll = function() {
|
||||
const length = $(this).scrollLeft;
|
||||
$('bulkRenameFilesTableFixedHeaderDiv').scrollLeft = length;
|
||||
$("bulkRenameFilesTableFixedHeaderDiv").scrollLeft = length;
|
||||
};
|
||||
|
||||
var handleTorrentFiles = function(files, selectedRows) {
|
||||
const rows = files.map(function(file, index) {
|
||||
const handleTorrentFiles = function(files, selectedRows) {
|
||||
const rows = files.map((file, index) => {
|
||||
|
||||
const row = {
|
||||
fileId: index,
|
||||
|
@ -325,20 +321,19 @@
|
|||
addRowsToTable(rows, selectedRows);
|
||||
};
|
||||
|
||||
var addRowsToTable = function(rows, selectedRows) {
|
||||
const addRowsToTable = function(rows, selectedRows) {
|
||||
let rowId = 0;
|
||||
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
||||
rootNode.autoCheckFolders = false;
|
||||
|
||||
rows.forEach(function(row) {
|
||||
rows.forEach((row) => {
|
||||
const pathItems = row.path.split(window.qBittorrent.Filesystem.PathSeparator);
|
||||
|
||||
pathItems.pop(); // remove last item (i.e. file name)
|
||||
let parent = rootNode;
|
||||
pathItems.forEach(function(folderName) {
|
||||
if (folderName === '.unwanted') {
|
||||
pathItems.forEach((folderName) => {
|
||||
if (folderName === ".unwanted")
|
||||
return;
|
||||
}
|
||||
|
||||
let folderNode = null;
|
||||
if (parent.children !== null) {
|
||||
|
@ -387,26 +382,24 @@
|
|||
bulkRenameFilesTable.updateTable(false);
|
||||
bulkRenameFilesTable.altRow();
|
||||
|
||||
if (selectedRows !== undefined) {
|
||||
if (selectedRows !== undefined)
|
||||
bulkRenameFilesTable.reselectRows(selectedRows);
|
||||
}
|
||||
|
||||
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
||||
fileRenamer.update();
|
||||
};
|
||||
|
||||
var setupTable = function(selectedRows) {
|
||||
const setupTable = function(selectedRows) {
|
||||
new Request.JSON({
|
||||
url: new URI('api/v2/torrents/files?hash=' + data.hash),
|
||||
url: new URI("api/v2/torrents/files?hash=" + data.hash),
|
||||
noCache: true,
|
||||
method: 'get',
|
||||
method: "get",
|
||||
onSuccess: function(files) {
|
||||
if (files.length === 0) {
|
||||
if (files.length === 0)
|
||||
bulkRenameFilesTable.clear();
|
||||
}
|
||||
else {
|
||||
else
|
||||
handleTorrentFiles(files, selectedRows);
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
setupTable(data.selectedRows);
|
||||
|
|
|
@ -9,57 +9,57 @@
|
|||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
new Keyboard({
|
||||
defaultEventType: 'keydown',
|
||||
defaultEventType: "keydown",
|
||||
events: {
|
||||
'Enter': (event) => {
|
||||
$('renameButton').click();
|
||||
"Enter": (event) => {
|
||||
$("renameButton").click();
|
||||
event.preventDefault();
|
||||
},
|
||||
'Escape': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
"Escape": (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||
event.preventDefault();
|
||||
},
|
||||
'Esc': (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
"Esc": (event) => {
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}).activate();
|
||||
window.addEvent('domready', () => {
|
||||
const oldName = new URI().getData('rule');
|
||||
window.addEvent("domready", () => {
|
||||
const oldName = new URI().getData("rule");
|
||||
|
||||
$('rename').value = oldName;
|
||||
$('rename').focus();
|
||||
$('rename').setSelectionRange(0, oldName.length);
|
||||
$("rename").value = oldName;
|
||||
$("rename").focus();
|
||||
$("rename").setSelectionRange(0, oldName.length);
|
||||
|
||||
$('renameButton').addEvent('click', (e) => {
|
||||
$("renameButton").addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
// check field
|
||||
const newName = $('rename').value.trim();
|
||||
if (newName === '') {
|
||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
||||
const newName = $("rename").value.trim();
|
||||
if (newName === "") {
|
||||
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName === oldName) {
|
||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
||||
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||
return;
|
||||
}
|
||||
|
||||
$('renameButton').disabled = true;
|
||||
$("renameButton").disabled = true;
|
||||
new Request({
|
||||
url: 'api/v2/rss/renameRule',
|
||||
method: 'post',
|
||||
url: "api/v2/rss/renameRule",
|
||||
method: "post",
|
||||
data: {
|
||||
ruleName: oldName,
|
||||
newRuleName: newName
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
||||
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
|
@ -46,7 +46,7 @@ window.qBittorrent.Cache = (() => {
|
|||
const keys = Reflect.ownKeys(obj);
|
||||
for (const key of keys) {
|
||||
const value = obj[key];
|
||||
if ((value && (typeof value === 'object')) || (typeof value === 'function'))
|
||||
if ((value && (typeof value === "object")) || (typeof value === "function"))
|
||||
deepFreeze(value);
|
||||
}
|
||||
Object.freeze(obj);
|
||||
|
@ -57,8 +57,8 @@ window.qBittorrent.Cache = (() => {
|
|||
|
||||
init() {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/app/buildInfo',
|
||||
method: 'get',
|
||||
url: "api/v2/app/buildInfo",
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onSuccess: (responseJSON) => {
|
||||
if (!responseJSON)
|
||||
|
@ -84,11 +84,11 @@ window.qBittorrent.Cache = (() => {
|
|||
// }
|
||||
init(obj = {}) {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/app/preferences',
|
||||
method: 'get',
|
||||
url: "api/v2/app/preferences",
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onFailure: (xhr) => {
|
||||
if (typeof obj.onFailure === 'function')
|
||||
if (typeof obj.onFailure === "function")
|
||||
obj.onFailure(xhr);
|
||||
},
|
||||
onSuccess: (responseJSON, responseText) => {
|
||||
|
@ -98,7 +98,7 @@ window.qBittorrent.Cache = (() => {
|
|||
deepFreeze(responseJSON);
|
||||
this.#m_store = responseJSON;
|
||||
|
||||
if (typeof obj.onSuccess === 'function')
|
||||
if (typeof obj.onSuccess === "function")
|
||||
obj.onSuccess(responseJSON, responseText);
|
||||
}
|
||||
}).send();
|
||||
|
@ -114,19 +114,19 @@ window.qBittorrent.Cache = (() => {
|
|||
// onSuccess: () => {}
|
||||
// }
|
||||
set(obj) {
|
||||
if (typeof obj !== 'object')
|
||||
throw new Error('`obj` is not an object.');
|
||||
if (typeof obj.data !== 'object')
|
||||
throw new Error('`data` is not an object.');
|
||||
if (typeof obj !== "object")
|
||||
throw new Error("`obj` is not an object.");
|
||||
if (typeof obj.data !== "object")
|
||||
throw new Error("`data` is not an object.");
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/app/setPreferences',
|
||||
method: 'post',
|
||||
url: "api/v2/app/setPreferences",
|
||||
method: "post",
|
||||
data: {
|
||||
'json': JSON.stringify(obj.data)
|
||||
"json": JSON.stringify(obj.data)
|
||||
},
|
||||
onFailure: (xhr) => {
|
||||
if (typeof obj.onFailure === 'function')
|
||||
if (typeof obj.onFailure === "function")
|
||||
obj.onFailure(xhr);
|
||||
},
|
||||
onSuccess: (responseText, responseXML) => {
|
||||
|
@ -140,7 +140,7 @@ window.qBittorrent.Cache = (() => {
|
|||
}
|
||||
deepFreeze(this.#m_store);
|
||||
|
||||
if (typeof obj.onSuccess === 'function')
|
||||
if (typeof obj.onSuccess === "function")
|
||||
obj.onSuccess(responseText, responseXML);
|
||||
}
|
||||
}).send();
|
||||
|
@ -148,12 +148,12 @@ window.qBittorrent.Cache = (() => {
|
|||
}
|
||||
|
||||
class QbtVersionCache {
|
||||
#m_store = '';
|
||||
#m_store = "";
|
||||
|
||||
init() {
|
||||
new Request({
|
||||
url: 'api/v2/app/version',
|
||||
method: 'get',
|
||||
url: "api/v2/app/version",
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onSuccess: (responseText) => {
|
||||
if (!responseText)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.ContextMenu = (function() {
|
||||
const exports = function() {
|
||||
|
@ -48,15 +47,15 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
|
||||
let lastShownContextMenu = null;
|
||||
const ContextMenu = new Class({
|
||||
//implements
|
||||
// implements
|
||||
Implements: [Options, Events],
|
||||
|
||||
//options
|
||||
// options
|
||||
options: {
|
||||
actions: {},
|
||||
menu: 'menu_id',
|
||||
menu: "menu_id",
|
||||
stopEvent: true,
|
||||
targets: 'body',
|
||||
targets: "body",
|
||||
offsets: {
|
||||
x: 0,
|
||||
y: 0
|
||||
|
@ -68,37 +67,35 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
touchTimer: 600
|
||||
},
|
||||
|
||||
//initialization
|
||||
// initialization
|
||||
initialize: function(options) {
|
||||
//set options
|
||||
// set options
|
||||
this.setOptions(options);
|
||||
|
||||
//option diffs menu
|
||||
// option diffs menu
|
||||
this.menu = $(this.options.menu);
|
||||
this.targets = $$(this.options.targets);
|
||||
|
||||
//fx
|
||||
// fx
|
||||
this.fx = new Fx.Tween(this.menu, {
|
||||
property: 'opacity',
|
||||
property: "opacity",
|
||||
duration: this.options.fadeSpeed,
|
||||
onComplete: function() {
|
||||
if (this.getStyle('opacity')) {
|
||||
this.setStyle('visibility', 'visible');
|
||||
}
|
||||
else {
|
||||
this.setStyle('visibility', 'hidden');
|
||||
}
|
||||
if (this.getStyle("opacity"))
|
||||
this.setStyle("visibility", "visible");
|
||||
else
|
||||
this.setStyle("visibility", "hidden");
|
||||
}.bind(this.menu)
|
||||
});
|
||||
|
||||
//hide and begin the listener
|
||||
// hide and begin the listener
|
||||
this.hide().startListener();
|
||||
|
||||
//hide the menu
|
||||
// hide the menu
|
||||
this.menu.setStyles({
|
||||
'position': 'absolute',
|
||||
'top': '-900000px',
|
||||
'display': 'block'
|
||||
"position": "absolute",
|
||||
"top": "-900000px",
|
||||
"display": "block"
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -107,13 +104,13 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
|
||||
const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75;
|
||||
|
||||
if (this.menu.hasClass('scrollableMenu'))
|
||||
this.menu.setStyle('max-height', scrollableMenuMaxHeight);
|
||||
if (this.menu.hasClass("scrollableMenu"))
|
||||
this.menu.setStyle("max-height", scrollableMenuMaxHeight);
|
||||
|
||||
// draw the menu off-screen to know the menu dimensions
|
||||
this.menu.setStyles({
|
||||
left: '-999em',
|
||||
top: '-999em'
|
||||
left: "-999em",
|
||||
top: "-999em"
|
||||
});
|
||||
|
||||
// position the menu
|
||||
|
@ -130,16 +127,16 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
this.menu.setStyles({
|
||||
left: xPosMenu,
|
||||
top: yPosMenu,
|
||||
position: 'absolute',
|
||||
'z-index': '2000'
|
||||
position: "absolute",
|
||||
"z-index": "2000"
|
||||
});
|
||||
|
||||
// position the sub-menu
|
||||
const uls = this.menu.getElementsByTagName('ul');
|
||||
const uls = this.menu.getElementsByTagName("ul");
|
||||
for (let i = 0; i < uls.length; ++i) {
|
||||
const ul = uls[i];
|
||||
if (ul.hasClass('scrollableMenu'))
|
||||
ul.setStyle('max-height', scrollableMenuMaxHeight);
|
||||
if (ul.hasClass("scrollableMenu"))
|
||||
ul.setStyle("max-height", scrollableMenuMaxHeight);
|
||||
const rectParent = ul.parentNode.getBoundingClientRect();
|
||||
const xPosOrigin = rectParent.left;
|
||||
const yPosOrigin = rectParent.bottom;
|
||||
|
@ -154,26 +151,26 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
if (yPos < 0)
|
||||
yPos = 0;
|
||||
ul.setStyles({
|
||||
'margin-left': xPos - xPosOrigin,
|
||||
'margin-top': yPos - yPosOrigin
|
||||
"margin-left": xPos - xPosOrigin,
|
||||
"margin-top": yPos - yPosOrigin
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setupEventListeners: function(elem) {
|
||||
elem.addEvent('contextmenu', function(e) {
|
||||
elem.addEvent("contextmenu", (e) => {
|
||||
this.triggerMenu(e, elem);
|
||||
}.bind(this));
|
||||
elem.addEvent('click', function(e) {
|
||||
});
|
||||
elem.addEvent("click", (e) => {
|
||||
this.hide();
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
elem.addEvent('touchstart', function(e) {
|
||||
elem.addEvent("touchstart", (e) => {
|
||||
this.hide();
|
||||
this.touchStartAt = performance.now();
|
||||
this.touchStartEvent = e;
|
||||
}.bind(this));
|
||||
elem.addEvent('touchend', function(e) {
|
||||
});
|
||||
elem.addEvent("touchend", (e) => {
|
||||
const now = performance.now();
|
||||
const touchStartAt = this.touchStartAt;
|
||||
const touchStartEvent = this.touchStartEvent;
|
||||
|
@ -182,16 +179,15 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
this.touchStartEvent = null;
|
||||
|
||||
const isTargetUnchanged = (Math.abs(e.event.pageX - touchStartEvent.event.pageX) <= 10) && (Math.abs(e.event.pageY - touchStartEvent.event.pageY) <= 10);
|
||||
if (((now - touchStartAt) >= this.options.touchTimer) && isTargetUnchanged) {
|
||||
if (((now - touchStartAt) >= this.options.touchTimer) && isTargetUnchanged)
|
||||
this.triggerMenu(touchStartEvent, elem);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
addTarget: function(t) {
|
||||
// prevent long press from selecting this text
|
||||
t.style.setProperty('user-select', 'none');
|
||||
t.style.setProperty('-webkit-user-select', 'none');
|
||||
t.style.setProperty("user-select", "none");
|
||||
t.style.setProperty("-webkit-user-select", "none");
|
||||
|
||||
this.targets[this.targets.length] = t;
|
||||
this.setupEventListeners(t);
|
||||
|
@ -201,102 +197,100 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
if (this.options.disabled)
|
||||
return;
|
||||
|
||||
//prevent default, if told to
|
||||
if (this.options.stopEvent) {
|
||||
// prevent default, if told to
|
||||
if (this.options.stopEvent)
|
||||
e.stop();
|
||||
}
|
||||
//record this as the trigger
|
||||
// record this as the trigger
|
||||
this.options.element = $(el);
|
||||
this.adjustMenuPosition(e);
|
||||
//show the menu
|
||||
// show the menu
|
||||
this.show();
|
||||
},
|
||||
|
||||
//get things started
|
||||
// get things started
|
||||
startListener: function() {
|
||||
/* all elements */
|
||||
this.targets.each(function(el) {
|
||||
this.targets.each((el) => {
|
||||
this.setupEventListeners(el);
|
||||
}.bind(this), this);
|
||||
|
||||
/* menu items */
|
||||
this.menu.getElements('a').each(function(item) {
|
||||
item.addEvent('click', function(e) {
|
||||
e.preventDefault();
|
||||
if (!item.hasClass('disabled')) {
|
||||
this.execute(item.get('href').split('#')[1], $(this.options.element));
|
||||
this.fireEvent('click', [item, e]);
|
||||
}
|
||||
}.bind(this));
|
||||
}, this);
|
||||
|
||||
//hide on body click
|
||||
$(document.body).addEvent('click', function() {
|
||||
/* menu items */
|
||||
this.menu.getElements("a").each(function(item) {
|
||||
item.addEvent("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (!item.hasClass("disabled")) {
|
||||
this.execute(item.get("href").split("#")[1], $(this.options.element));
|
||||
this.fireEvent("click", [item, e]);
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
|
||||
// hide on body click
|
||||
$(document.body).addEvent("click", () => {
|
||||
this.hide();
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
updateMenuItems: function() {},
|
||||
|
||||
//show menu
|
||||
// show menu
|
||||
show: function(trigger) {
|
||||
if (lastShownContextMenu && (lastShownContextMenu !== this))
|
||||
lastShownContextMenu.hide();
|
||||
this.fx.start(1);
|
||||
this.fireEvent('show');
|
||||
this.fireEvent("show");
|
||||
lastShownContextMenu = this;
|
||||
return this;
|
||||
},
|
||||
|
||||
//hide the menu
|
||||
// hide the menu
|
||||
hide: function(trigger) {
|
||||
if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== 'hidden')) {
|
||||
if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== "hidden")) {
|
||||
this.fx.start(0);
|
||||
//this.menu.fade('out');
|
||||
this.fireEvent('hide');
|
||||
// this.menu.fade('out');
|
||||
this.fireEvent("hide");
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setItemChecked: function(item, checked) {
|
||||
this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity =
|
||||
checked ? '1' : '0';
|
||||
this.menu.getElement("a[href$=" + item + "]").firstChild.style.opacity =
|
||||
checked ? "1" : "0";
|
||||
return this;
|
||||
},
|
||||
|
||||
getItemChecked: function(item) {
|
||||
return this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity !== '0';
|
||||
return this.menu.getElement("a[href$=" + item + "]").firstChild.style.opacity !== "0";
|
||||
},
|
||||
|
||||
//hide an item
|
||||
// hide an item
|
||||
hideItem: function(item) {
|
||||
this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible');
|
||||
this.menu.getElement("a[href$=" + item + "]").parentNode.addClass("invisible");
|
||||
return this;
|
||||
},
|
||||
|
||||
//show an item
|
||||
// show an item
|
||||
showItem: function(item) {
|
||||
this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible');
|
||||
this.menu.getElement("a[href$=" + item + "]").parentNode.removeClass("invisible");
|
||||
return this;
|
||||
},
|
||||
|
||||
//disable the entire menu
|
||||
// disable the entire menu
|
||||
disable: function() {
|
||||
this.options.disabled = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
//enable the entire menu
|
||||
// enable the entire menu
|
||||
enable: function() {
|
||||
this.options.disabled = false;
|
||||
return this;
|
||||
},
|
||||
|
||||
//execute an action
|
||||
// execute an action
|
||||
execute: function(action, element) {
|
||||
if (this.options.actions[action]) {
|
||||
if (this.options.actions[action])
|
||||
this.options.actions[action](element, this, action);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
@ -323,37 +317,37 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
selectedRows.forEach((item, index) => {
|
||||
const data = torrentsTable.rows.get(item).full_data;
|
||||
|
||||
if (data['seq_dl'] !== true)
|
||||
if (data["seq_dl"] !== true)
|
||||
all_are_seq_dl = false;
|
||||
else
|
||||
there_are_seq_dl = true;
|
||||
|
||||
if (data['f_l_piece_prio'] !== true)
|
||||
if (data["f_l_piece_prio"] !== true)
|
||||
all_are_f_l_piece_prio = false;
|
||||
else
|
||||
there_are_f_l_piece_prio = true;
|
||||
|
||||
if (data['progress'] !== 1.0) // not downloaded
|
||||
if (data["progress"] !== 1.0) // not downloaded
|
||||
all_are_downloaded = false;
|
||||
else if (data['super_seeding'] !== true)
|
||||
else if (data["super_seeding"] !== true)
|
||||
all_are_super_seeding = false;
|
||||
|
||||
if ((data['state'] !== 'stoppedUP') && (data['state'] !== 'stoppedDL'))
|
||||
if ((data["state"] !== "stoppedUP") && (data["state"] !== "stoppedDL"))
|
||||
all_are_stopped = false;
|
||||
else
|
||||
there_are_stopped = true;
|
||||
|
||||
if (data['force_start'] !== true)
|
||||
if (data["force_start"] !== true)
|
||||
all_are_force_start = false;
|
||||
else
|
||||
there_are_force_start = true;
|
||||
|
||||
if (data['auto_tmm'] === true)
|
||||
if (data["auto_tmm"] === true)
|
||||
there_are_auto_tmm = true;
|
||||
else
|
||||
all_are_auto_tmm = false;
|
||||
|
||||
const torrentTags = data['tags'].split(', ');
|
||||
const torrentTags = data["tags"].split(", ");
|
||||
for (const tag of torrentTags) {
|
||||
const count = tagCount.get(tag);
|
||||
tagCount.set(tag, ((count !== undefined) ? (count + 1) : 1));
|
||||
|
@ -363,71 +357,71 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
// hide renameFiles when more than 1 torrent is selected
|
||||
if (selectedRows.length === 1) {
|
||||
const data = torrentsTable.rows.get(selectedRows[0]).full_data;
|
||||
let metadata_downloaded = !((data['state'] === 'metaDL') || (data['state'] === 'forcedMetaDL') || (data['total_size'] === -1));
|
||||
const metadata_downloaded = !((data["state"] === "metaDL") || (data["state"] === "forcedMetaDL") || (data["total_size"] === -1));
|
||||
|
||||
// hide renameFiles when metadata hasn't been downloaded yet
|
||||
metadata_downloaded
|
||||
? this.showItem('renameFiles')
|
||||
: this.hideItem('renameFiles');
|
||||
? this.showItem("renameFiles")
|
||||
: this.hideItem("renameFiles");
|
||||
}
|
||||
else {
|
||||
this.hideItem('renameFiles');
|
||||
this.hideItem("renameFiles");
|
||||
}
|
||||
|
||||
if (all_are_downloaded) {
|
||||
this.hideItem('downloadLimit');
|
||||
this.menu.getElement('a[href$=uploadLimit]').parentNode.addClass('separator');
|
||||
this.hideItem('sequentialDownload');
|
||||
this.hideItem('firstLastPiecePrio');
|
||||
this.showItem('superSeeding');
|
||||
this.setItemChecked('superSeeding', all_are_super_seeding);
|
||||
this.hideItem("downloadLimit");
|
||||
this.menu.getElement("a[href$=uploadLimit]").parentNode.addClass("separator");
|
||||
this.hideItem("sequentialDownload");
|
||||
this.hideItem("firstLastPiecePrio");
|
||||
this.showItem("superSeeding");
|
||||
this.setItemChecked("superSeeding", all_are_super_seeding);
|
||||
}
|
||||
else {
|
||||
const show_seq_dl = (all_are_seq_dl || !there_are_seq_dl);
|
||||
const show_f_l_piece_prio = (all_are_f_l_piece_prio || !there_are_f_l_piece_prio);
|
||||
|
||||
if (!show_seq_dl && show_f_l_piece_prio)
|
||||
this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.addClass('separator');
|
||||
this.menu.getElement("a[href$=firstLastPiecePrio]").parentNode.addClass("separator");
|
||||
else
|
||||
this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.removeClass('separator');
|
||||
this.menu.getElement("a[href$=firstLastPiecePrio]").parentNode.removeClass("separator");
|
||||
|
||||
if (show_seq_dl)
|
||||
this.showItem('sequentialDownload');
|
||||
this.showItem("sequentialDownload");
|
||||
else
|
||||
this.hideItem('sequentialDownload');
|
||||
this.hideItem("sequentialDownload");
|
||||
|
||||
if (show_f_l_piece_prio)
|
||||
this.showItem('firstLastPiecePrio');
|
||||
this.showItem("firstLastPiecePrio");
|
||||
else
|
||||
this.hideItem('firstLastPiecePrio');
|
||||
this.hideItem("firstLastPiecePrio");
|
||||
|
||||
this.setItemChecked('sequentialDownload', all_are_seq_dl);
|
||||
this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio);
|
||||
this.setItemChecked("sequentialDownload", all_are_seq_dl);
|
||||
this.setItemChecked("firstLastPiecePrio", all_are_f_l_piece_prio);
|
||||
|
||||
this.showItem('downloadLimit');
|
||||
this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator');
|
||||
this.hideItem('superSeeding');
|
||||
this.showItem("downloadLimit");
|
||||
this.menu.getElement("a[href$=uploadLimit]").parentNode.removeClass("separator");
|
||||
this.hideItem("superSeeding");
|
||||
}
|
||||
|
||||
this.showItem('start');
|
||||
this.showItem('stop');
|
||||
this.showItem('forceStart');
|
||||
this.showItem("start");
|
||||
this.showItem("stop");
|
||||
this.showItem("forceStart");
|
||||
if (all_are_stopped)
|
||||
this.hideItem('stop');
|
||||
this.hideItem("stop");
|
||||
else if (all_are_force_start)
|
||||
this.hideItem('forceStart');
|
||||
this.hideItem("forceStart");
|
||||
else if (!there_are_stopped && !there_are_force_start)
|
||||
this.hideItem('start');
|
||||
this.hideItem("start");
|
||||
|
||||
if (!all_are_auto_tmm && there_are_auto_tmm) {
|
||||
this.hideItem('autoTorrentManagement');
|
||||
this.hideItem("autoTorrentManagement");
|
||||
}
|
||||
else {
|
||||
this.showItem('autoTorrentManagement');
|
||||
this.setItemChecked('autoTorrentManagement', all_are_auto_tmm);
|
||||
this.showItem("autoTorrentManagement");
|
||||
this.setItemChecked("autoTorrentManagement", all_are_auto_tmm);
|
||||
}
|
||||
|
||||
const contextTagList = $('contextTagList');
|
||||
const contextTagList = $("contextTagList");
|
||||
tagList.forEach((tag, tagHash) => {
|
||||
const checkbox = contextTagList.getElement(`a[href="#Tag/${tagHash}"] input[type="checkbox"]`);
|
||||
const count = tagCount.get(tag.name);
|
||||
|
@ -439,12 +433,12 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
},
|
||||
|
||||
updateCategoriesSubMenu: function(categoryList) {
|
||||
const contextCategoryList = $('contextCategoryList');
|
||||
const contextCategoryList = $("contextCategoryList");
|
||||
contextCategoryList.getChildren().each(c => c.destroy());
|
||||
contextCategoryList.appendChild(new Element('li', {
|
||||
contextCategoryList.appendChild(new Element("li", {
|
||||
html: '<a href="javascript:torrentNewCategoryFN();"><img src="images/list-add.svg" alt="QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]"/> QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]</a>'
|
||||
}));
|
||||
contextCategoryList.appendChild(new Element('li', {
|
||||
contextCategoryList.appendChild(new Element("li", {
|
||||
html: '<a href="javascript:torrentSetCategoryFN(0);"><img src="images/edit-clear.svg" alt="QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]"/> QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]</a>'
|
||||
}));
|
||||
|
||||
|
@ -458,11 +452,11 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
|
||||
let first = true;
|
||||
for (const { categoryName, categoryHash } of sortedCategories) {
|
||||
const el = new Element('li', {
|
||||
const el = new Element("li", {
|
||||
html: `<a href="javascript:torrentSetCategoryFN(${categoryHash});"><img src="images/view-categories.svg"/>${window.qBittorrent.Misc.escapeHtml(categoryName)}</a>`
|
||||
});
|
||||
if (first) {
|
||||
el.addClass('separator');
|
||||
el.addClass("separator");
|
||||
first = false;
|
||||
}
|
||||
contextCategoryList.appendChild(el);
|
||||
|
@ -470,21 +464,21 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
},
|
||||
|
||||
updateTagsSubMenu: function(tagList) {
|
||||
const contextTagList = $('contextTagList');
|
||||
const contextTagList = $("contextTagList");
|
||||
while (contextTagList.firstChild !== null)
|
||||
contextTagList.removeChild(contextTagList.firstChild);
|
||||
|
||||
contextTagList.appendChild(new Element('li', {
|
||||
contextTagList.appendChild(new Element("li", {
|
||||
html: '<a href="javascript:torrentAddTagsFN();">'
|
||||
+ '<img src="images/list-add.svg" alt="QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
||||
+ ' QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]'
|
||||
+ '</a>'
|
||||
+ " QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"
|
||||
+ "</a>"
|
||||
}));
|
||||
contextTagList.appendChild(new Element('li', {
|
||||
contextTagList.appendChild(new Element("li", {
|
||||
html: '<a href="javascript:torrentRemoveAllTagsFN();">'
|
||||
+ '<img src="images/edit-clear.svg" alt="QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
||||
+ ' QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]'
|
||||
+ '</a>'
|
||||
+ " QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"
|
||||
+ "</a>"
|
||||
}));
|
||||
|
||||
const sortedTags = [];
|
||||
|
@ -496,13 +490,13 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
|
||||
for (let i = 0; i < sortedTags.length; ++i) {
|
||||
const { tagName, tagHash } = sortedTags[i];
|
||||
const el = new Element('li', {
|
||||
const el = new Element("li", {
|
||||
html: `<a href="#Tag/${tagHash}" onclick="event.preventDefault(); torrentSetTagsFN(${tagHash}, !event.currentTarget.getElement('input[type=checkbox]').checked);">`
|
||||
+ '<input type="checkbox" onclick="this.checked = !this.checked;"> ' + window.qBittorrent.Misc.escapeHtml(tagName)
|
||||
+ '</a>'
|
||||
+ "</a>"
|
||||
});
|
||||
if (i === 0)
|
||||
el.addClass('separator');
|
||||
el.addClass("separator");
|
||||
contextTagList.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
@ -513,19 +507,17 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
updateMenuItems: function() {
|
||||
const id = Number(this.options.element.id);
|
||||
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
|
||||
this.showItem('editCategory');
|
||||
this.showItem('deleteCategory');
|
||||
if (useSubcategories) {
|
||||
this.showItem('createSubcategory');
|
||||
this.showItem("editCategory");
|
||||
this.showItem("deleteCategory");
|
||||
if (useSubcategories)
|
||||
this.showItem("createSubcategory");
|
||||
else
|
||||
this.hideItem("createSubcategory");
|
||||
}
|
||||
else {
|
||||
this.hideItem('createSubcategory');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.hideItem('editCategory');
|
||||
this.hideItem('deleteCategory');
|
||||
this.hideItem('createSubcategory');
|
||||
this.hideItem("editCategory");
|
||||
this.hideItem("deleteCategory");
|
||||
this.hideItem("createSubcategory");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -535,9 +527,9 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
updateMenuItems: function() {
|
||||
const id = Number(this.options.element.id);
|
||||
if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
|
||||
this.showItem('deleteTag');
|
||||
this.showItem("deleteTag");
|
||||
else
|
||||
this.hideItem('deleteTag');
|
||||
this.hideItem("deleteTag");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -547,82 +539,83 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
updateMenuItems: function() {
|
||||
const enabledColumnIndex = function(text) {
|
||||
const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th");
|
||||
for (let i = 0; i < columns.length; ++i)
|
||||
for (let i = 0; i < columns.length; ++i) {
|
||||
if (columns[i].get("html") === "Enabled")
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
this.showItem('Enabled');
|
||||
this.setItemChecked('Enabled', this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes");
|
||||
this.showItem("Enabled");
|
||||
this.setItemChecked("Enabled", this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes");
|
||||
|
||||
this.showItem('Uninstall');
|
||||
this.showItem("Uninstall");
|
||||
}
|
||||
});
|
||||
|
||||
const RssFeedContextMenu = new Class({
|
||||
Extends: ContextMenu,
|
||||
updateMenuItems: function() {
|
||||
let selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
|
||||
this.menu.getElement('a[href$=newSubscription]').parentNode.addClass('separator');
|
||||
const selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
|
||||
this.menu.getElement("a[href$=newSubscription]").parentNode.addClass("separator");
|
||||
switch (selectedRows.length) {
|
||||
case 0:
|
||||
// remove separator on top of newSubscription entry to avoid double line
|
||||
this.menu.getElement('a[href$=newSubscription]').parentNode.removeClass('separator');
|
||||
this.menu.getElement("a[href$=newSubscription]").parentNode.removeClass("separator");
|
||||
// menu when nothing selected
|
||||
this.hideItem('update');
|
||||
this.hideItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.hideItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.showItem('newFolder');
|
||||
this.showItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
this.hideItem("update");
|
||||
this.hideItem("markRead");
|
||||
this.hideItem("rename");
|
||||
this.hideItem("delete");
|
||||
this.showItem("newSubscription");
|
||||
this.showItem("newFolder");
|
||||
this.showItem("updateAll");
|
||||
this.hideItem("copyFeedURL");
|
||||
break;
|
||||
case 1:
|
||||
if (selectedRows[0] === 0) {
|
||||
// menu when "unread" feed selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.hideItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
this.showItem("update");
|
||||
this.showItem("markRead");
|
||||
this.hideItem("rename");
|
||||
this.hideItem("delete");
|
||||
this.showItem("newSubscription");
|
||||
this.hideItem("newFolder");
|
||||
this.hideItem("updateAll");
|
||||
this.hideItem("copyFeedURL");
|
||||
}
|
||||
else if (window.qBittorrent.Rss.rssFeedTable.rows[selectedRows[0]].full_data.dataUid === '') {
|
||||
else if (window.qBittorrent.Rss.rssFeedTable.rows[selectedRows[0]].full_data.dataUid === "") {
|
||||
// menu when single folder selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.showItem('rename');
|
||||
this.showItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.showItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.hideItem('copyFeedURL');
|
||||
this.showItem("update");
|
||||
this.showItem("markRead");
|
||||
this.showItem("rename");
|
||||
this.showItem("delete");
|
||||
this.showItem("newSubscription");
|
||||
this.showItem("newFolder");
|
||||
this.hideItem("updateAll");
|
||||
this.hideItem("copyFeedURL");
|
||||
}
|
||||
else {
|
||||
// menu when single feed selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.showItem('rename');
|
||||
this.showItem('delete');
|
||||
this.showItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.showItem('copyFeedURL');
|
||||
this.showItem("update");
|
||||
this.showItem("markRead");
|
||||
this.showItem("rename");
|
||||
this.showItem("delete");
|
||||
this.showItem("newSubscription");
|
||||
this.hideItem("newFolder");
|
||||
this.hideItem("updateAll");
|
||||
this.showItem("copyFeedURL");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// menu when multiple items selected
|
||||
this.showItem('update');
|
||||
this.showItem('markRead');
|
||||
this.hideItem('rename');
|
||||
this.showItem('delete');
|
||||
this.hideItem('newSubscription');
|
||||
this.hideItem('newFolder');
|
||||
this.hideItem('updateAll');
|
||||
this.showItem('copyFeedURL');
|
||||
this.showItem("update");
|
||||
this.showItem("markRead");
|
||||
this.hideItem("rename");
|
||||
this.showItem("delete");
|
||||
this.hideItem("newSubscription");
|
||||
this.hideItem("newFolder");
|
||||
this.hideItem("updateAll");
|
||||
this.showItem("copyFeedURL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -639,12 +632,12 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
|
||||
// draw the menu off-screen to know the menu dimensions
|
||||
this.menu.setStyles({
|
||||
left: '-999em',
|
||||
top: '-999em'
|
||||
left: "-999em",
|
||||
top: "-999em"
|
||||
});
|
||||
// position the menu
|
||||
let xPosMenu = e.page.x + this.options.offsets.x - $('rssdownloaderpage').offsetLeft;
|
||||
let yPosMenu = e.page.y + this.options.offsets.y - $('rssdownloaderpage').offsetTop;
|
||||
let xPosMenu = e.page.x + this.options.offsets.x - $("rssdownloaderpage").offsetLeft;
|
||||
let yPosMenu = e.page.y + this.options.offsets.y - $("rssdownloaderpage").offsetTop;
|
||||
if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
|
||||
xPosMenu -= this.menu.offsetWidth;
|
||||
if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
|
||||
|
@ -655,31 +648,31 @@ window.qBittorrent.ContextMenu = (function() {
|
|||
this.menu.setStyles({
|
||||
left: xPosMenu,
|
||||
top: yPosMenu,
|
||||
position: 'absolute',
|
||||
'z-index': '2000'
|
||||
position: "absolute",
|
||||
"z-index": "2000"
|
||||
});
|
||||
},
|
||||
updateMenuItems: function() {
|
||||
let selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
|
||||
this.showItem('addRule');
|
||||
const selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
|
||||
this.showItem("addRule");
|
||||
switch (selectedRows.length) {
|
||||
case 0:
|
||||
// menu when nothing selected
|
||||
this.hideItem('deleteRule');
|
||||
this.hideItem('renameRule');
|
||||
this.hideItem('clearDownloadedEpisodes');
|
||||
this.hideItem("deleteRule");
|
||||
this.hideItem("renameRule");
|
||||
this.hideItem("clearDownloadedEpisodes");
|
||||
break;
|
||||
case 1:
|
||||
// menu when single item selected
|
||||
this.showItem('deleteRule');
|
||||
this.showItem('renameRule');
|
||||
this.showItem('clearDownloadedEpisodes');
|
||||
this.showItem("deleteRule");
|
||||
this.showItem("renameRule");
|
||||
this.showItem("clearDownloadedEpisodes");
|
||||
break;
|
||||
default:
|
||||
// menu when multiple items selected
|
||||
this.showItem('deleteRule');
|
||||
this.hideItem('renameRule');
|
||||
this.showItem('clearDownloadedEpisodes');
|
||||
this.showItem("deleteRule");
|
||||
this.hideItem("renameRule");
|
||||
this.showItem("clearDownloadedEpisodes");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,10 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.Download = (function() {
|
||||
const exports = function() {
|
||||
|
@ -40,18 +39,21 @@ window.qBittorrent.Download = (function() {
|
|||
|
||||
const getCategories = function() {
|
||||
new Request.JSON({
|
||||
url: 'api/v2/torrents/categories',
|
||||
method: 'get',
|
||||
url: "api/v2/torrents/categories",
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onSuccess: function(data) {
|
||||
if (data) {
|
||||
categories = data;
|
||||
for (const i in data) {
|
||||
if (!Object.hasOwn(data, i))
|
||||
continue;
|
||||
|
||||
const category = data[i];
|
||||
const option = new Element("option");
|
||||
option.set('value', category.name);
|
||||
option.set('html', category.name);
|
||||
$('categorySelect').appendChild(option);
|
||||
option.set("value", category.name);
|
||||
option.set("html", category.name);
|
||||
$("categorySelect").appendChild(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,37 +64,31 @@ window.qBittorrent.Download = (function() {
|
|||
const pref = window.parent.qBittorrent.Cache.preferences.get();
|
||||
|
||||
defaultSavePath = pref.save_path;
|
||||
$('savepath').setProperty('value', defaultSavePath);
|
||||
$('startTorrent').checked = !pref.add_stopped_enabled;
|
||||
$('addToTopOfQueue').checked = pref.add_to_top_of_queue;
|
||||
$("savepath").setProperty("value", defaultSavePath);
|
||||
$("startTorrent").checked = !pref.add_stopped_enabled;
|
||||
$("addToTopOfQueue").checked = pref.add_to_top_of_queue;
|
||||
|
||||
if (pref.auto_tmm_enabled === 1) {
|
||||
$('autoTMM').selectedIndex = 1;
|
||||
$('savepath').disabled = true;
|
||||
if (pref.auto_tmm_enabled) {
|
||||
$("autoTMM").selectedIndex = 1;
|
||||
$("savepath").disabled = true;
|
||||
}
|
||||
else {
|
||||
$('autoTMM').selectedIndex = 0;
|
||||
$("autoTMM").selectedIndex = 0;
|
||||
}
|
||||
|
||||
if (pref.torrent_stop_condition === "MetadataReceived") {
|
||||
$('stopCondition').selectedIndex = 1;
|
||||
}
|
||||
else if (pref.torrent_stop_condition === "FilesChecked") {
|
||||
$('stopCondition').selectedIndex = 2;
|
||||
}
|
||||
else {
|
||||
$('stopCondition').selectedIndex = 0;
|
||||
}
|
||||
if (pref.torrent_stop_condition === "MetadataReceived")
|
||||
$("stopCondition").selectedIndex = 1;
|
||||
else if (pref.torrent_stop_condition === "FilesChecked")
|
||||
$("stopCondition").selectedIndex = 2;
|
||||
else
|
||||
$("stopCondition").selectedIndex = 0;
|
||||
|
||||
if (pref.torrent_content_layout === "Subfolder") {
|
||||
$('contentLayout').selectedIndex = 1;
|
||||
}
|
||||
else if (pref.torrent_content_layout === "NoSubfolder") {
|
||||
$('contentLayout').selectedIndex = 2;
|
||||
}
|
||||
else {
|
||||
$('contentLayout').selectedIndex = 0;
|
||||
}
|
||||
if (pref.torrent_content_layout === "Subfolder")
|
||||
$("contentLayout").selectedIndex = 1;
|
||||
else if (pref.torrent_content_layout === "NoSubfolder")
|
||||
$("contentLayout").selectedIndex = 2;
|
||||
else
|
||||
$("contentLayout").selectedIndex = 0;
|
||||
};
|
||||
|
||||
const changeCategorySelect = function(item) {
|
||||
|
@ -101,41 +97,41 @@ window.qBittorrent.Download = (function() {
|
|||
item.nextElementSibling.value = "";
|
||||
item.nextElementSibling.select();
|
||||
|
||||
if ($('autoTMM').selectedIndex === 1)
|
||||
$('savepath').value = defaultSavePath;
|
||||
if ($("autoTMM").selectedIndex === 1)
|
||||
$("savepath").value = defaultSavePath;
|
||||
}
|
||||
else {
|
||||
item.nextElementSibling.hidden = true;
|
||||
const text = item.options[item.selectedIndex].textContent;
|
||||
item.nextElementSibling.value = text;
|
||||
|
||||
if ($('autoTMM').selectedIndex === 1) {
|
||||
if ($("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;
|
||||
savePath = (category["savePath"] !== "") ? category["savePath"] : `${defaultSavePath}/${categoryName}`;
|
||||
$("savepath").value = savePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeTMM = function(item) {
|
||||
if (item.selectedIndex === 1) {
|
||||
$('savepath').disabled = true;
|
||||
$("savepath").disabled = true;
|
||||
|
||||
const categorySelect = $('categorySelect');
|
||||
const categorySelect = $("categorySelect");
|
||||
const categoryName = categorySelect.options[categorySelect.selectedIndex].value;
|
||||
const category = categories[categoryName];
|
||||
$('savepath').value = (category === undefined) ? "" : category['savePath'];
|
||||
$("savepath").value = (category === undefined) ? "" : category["savePath"];
|
||||
}
|
||||
else {
|
||||
$('savepath').disabled = false;
|
||||
$('savepath').value = defaultSavePath;
|
||||
$("savepath").disabled = false;
|
||||
$("savepath").value = defaultSavePath;
|
||||
}
|
||||
};
|
||||
|
||||
$(window).addEventListener("load", function() {
|
||||
$(window).addEventListener("load", () => {
|
||||
getPreferences();
|
||||
getCategories();
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.FileTree = (function() {
|
||||
const exports = function() {
|
||||
|
@ -77,13 +76,12 @@ window.qBittorrent.FileTree = (function() {
|
|||
|
||||
generateNodeMap: function(node) {
|
||||
// don't store root node in map
|
||||
if (node.root !== null) {
|
||||
if (node.root !== null)
|
||||
this.nodeMap[node.rowId] = node;
|
||||
}
|
||||
|
||||
node.children.each(function(child) {
|
||||
node.children.each((child) => {
|
||||
this.generateNodeMap(child);
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
getNode: function(rowId) {
|
||||
|
@ -101,17 +99,17 @@ window.qBittorrent.FileTree = (function() {
|
|||
*/
|
||||
toArray: function() {
|
||||
const nodes = [];
|
||||
this.root.children.each(function(node) {
|
||||
this.root.children.each((node) => {
|
||||
this._getArrayOfNodes(node, nodes);
|
||||
}.bind(this));
|
||||
});
|
||||
return nodes;
|
||||
},
|
||||
|
||||
_getArrayOfNodes: function(node, array) {
|
||||
array.push(node);
|
||||
node.children.each(function(child) {
|
||||
node.children.each((child) => {
|
||||
this._getArrayOfNodes(child, array);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -161,7 +159,7 @@ window.qBittorrent.FileTree = (function() {
|
|||
|
||||
let isFirstFile = true;
|
||||
|
||||
this.children.each(function(node) {
|
||||
this.children.each((node) => {
|
||||
if (node.isFolder)
|
||||
node.calculateSize();
|
||||
|
||||
|
@ -185,7 +183,7 @@ window.qBittorrent.FileTree = (function() {
|
|||
progress += (node.progress * node.size);
|
||||
availability += (node.availability * node.size);
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this.size = size;
|
||||
this.remaining = remaining;
|
||||
|
|
|
@ -26,13 +26,12 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// This file is the JavaScript implementation of base/utils/fs.cpp
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.Filesystem = (function() {
|
||||
const exports = function() {
|
||||
|
@ -44,15 +43,15 @@ window.qBittorrent.Filesystem = (function() {
|
|||
};
|
||||
};
|
||||
|
||||
const PathSeparator = '/';
|
||||
const PathSeparator = "/";
|
||||
|
||||
/**
|
||||
* Returns the file extension part of a file name.
|
||||
*/
|
||||
const fileExtension = function(filename) {
|
||||
const pointIndex = filename.lastIndexOf('.');
|
||||
const pointIndex = filename.lastIndexOf(".");
|
||||
if (pointIndex === -1)
|
||||
return '';
|
||||
return "";
|
||||
return filename.substring(pointIndex + 1);
|
||||
};
|
||||
|
||||
|
@ -66,7 +65,7 @@ window.qBittorrent.Filesystem = (function() {
|
|||
const folderName = function(filepath) {
|
||||
const slashIndex = filepath.lastIndexOf(PathSeparator);
|
||||
if (slashIndex === -1)
|
||||
return '';
|
||||
return "";
|
||||
return filepath.substring(0, slashIndex);
|
||||
};
|
||||
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.LocalPreferences = (function() {
|
||||
const exports = function() {
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.Misc = (function() {
|
||||
const exports = function() {
|
||||
|
@ -86,8 +85,9 @@ window.qBittorrent.Misc = (function() {
|
|||
}
|
||||
|
||||
let ret;
|
||||
if (i === 0)
|
||||
if (i === 0) {
|
||||
ret = value + " " + units[i];
|
||||
}
|
||||
else {
|
||||
const precision = friendlyUnitPrecision(i);
|
||||
const offset = Math.pow(10, precision);
|
||||
|
@ -112,18 +112,18 @@ window.qBittorrent.Misc = (function() {
|
|||
return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]";
|
||||
let minutes = seconds / 60;
|
||||
if (minutes < 60)
|
||||
return "QBT_TR(%1m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(minutes));
|
||||
return "QBT_TR(%1m)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(minutes));
|
||||
let hours = minutes / 60;
|
||||
minutes = minutes % 60;
|
||||
minutes %= 60;
|
||||
if (hours < 24)
|
||||
return "QBT_TR(%1h %2m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(hours)).replace("%2", parseInt(minutes));
|
||||
return "QBT_TR(%1h %2m)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(hours)).replace("%2", Math.floor(minutes));
|
||||
let days = hours / 24;
|
||||
hours = hours % 24;
|
||||
hours %= 24;
|
||||
if (days < 365)
|
||||
return "QBT_TR(%1d %2h)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(days)).replace("%2", parseInt(hours));
|
||||
return "QBT_TR(%1d %2h)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(days)).replace("%2", Math.floor(hours));
|
||||
const years = days / 365;
|
||||
days = days % 365;
|
||||
return "QBT_TR(%1y %2d)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(years)).replace("%2", parseInt(days));
|
||||
days %= 365;
|
||||
return "QBT_TR(%1y %2d)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(years)).replace("%2", Math.floor(days));
|
||||
};
|
||||
|
||||
const friendlyPercentage = function(value) {
|
||||
|
@ -152,7 +152,7 @@ window.qBittorrent.Misc = (function() {
|
|||
valid: false
|
||||
};
|
||||
|
||||
if (typeof versionString !== 'string')
|
||||
if (typeof versionString !== "string")
|
||||
return failure;
|
||||
|
||||
const tryToNumber = (str) => {
|
||||
|
@ -160,7 +160,7 @@ window.qBittorrent.Misc = (function() {
|
|||
return (isNaN(num) ? str : num);
|
||||
};
|
||||
|
||||
const ver = versionString.split('.', 4).map(val => tryToNumber(val));
|
||||
const ver = versionString.split(".", 4).map(val => tryToNumber(val));
|
||||
return {
|
||||
valid: true,
|
||||
major: ver[0],
|
||||
|
@ -171,7 +171,7 @@ window.qBittorrent.Misc = (function() {
|
|||
};
|
||||
|
||||
const escapeHtml = function(str) {
|
||||
const div = document.createElement('div');
|
||||
const div = document.createElement("div");
|
||||
div.appendChild(document.createTextNode(str));
|
||||
const escapedString = div.innerHTML;
|
||||
div.remove();
|
||||
|
@ -179,7 +179,7 @@ window.qBittorrent.Misc = (function() {
|
|||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#parameters
|
||||
const naturalSortCollator = new Intl.Collator(undefined, { numeric: true, usage: 'sort' });
|
||||
const naturalSortCollator = new Intl.Collator(undefined, { numeric: true, usage: "sort" });
|
||||
|
||||
const safeTrim = function(value) {
|
||||
try {
|
||||
|
@ -206,9 +206,9 @@ window.qBittorrent.Misc = (function() {
|
|||
*/
|
||||
const containsAllTerms = function(text, terms) {
|
||||
const textToSearch = text.toLowerCase();
|
||||
return terms.every(function(term) {
|
||||
const isTermRequired = (term[0] === '+');
|
||||
const isTermExcluded = (term[0] === '-');
|
||||
return terms.every((term) => {
|
||||
const isTermRequired = (term[0] === "+");
|
||||
const isTermExcluded = (term[0] === "-");
|
||||
if (isTermRequired || isTermExcluded) {
|
||||
// ignore lonely +/-
|
||||
if (term.length === 1)
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
it in the onContentLoaded function of the new window.
|
||||
|
||||
----------------------------------------------------------------- */
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass();
|
||||
|
||||
|
@ -96,45 +96,43 @@ let exportTorrentFN = function() {};
|
|||
const initializeWindows = function() {
|
||||
saveWindowSize = function(windowId) {
|
||||
const size = $(windowId).getSize();
|
||||
LocalPreferences.set('window_' + windowId + '_width', size.x);
|
||||
LocalPreferences.set('window_' + windowId + '_height', size.y);
|
||||
LocalPreferences.set("window_" + windowId + "_width", size.x);
|
||||
LocalPreferences.set("window_" + windowId + "_height", size.y);
|
||||
};
|
||||
|
||||
loadWindowWidth = function(windowId, defaultValue) {
|
||||
return LocalPreferences.get('window_' + windowId + '_width', defaultValue);
|
||||
return LocalPreferences.get("window_" + windowId + "_width", defaultValue);
|
||||
};
|
||||
|
||||
loadWindowHeight = function(windowId, defaultValue) {
|
||||
return LocalPreferences.get('window_' + windowId + '_height', defaultValue);
|
||||
return LocalPreferences.get("window_" + windowId + "_height", defaultValue);
|
||||
};
|
||||
|
||||
function addClickEvent(el, fn) {
|
||||
['Link', 'Button'].each(function(item) {
|
||||
if ($(el + item)) {
|
||||
$(el + item).addEvent('click', fn);
|
||||
}
|
||||
["Link", "Button"].each((item) => {
|
||||
if ($(el + item))
|
||||
$(el + item).addEvent("click", fn);
|
||||
});
|
||||
}
|
||||
|
||||
addClickEvent('download', function(e) {
|
||||
addClickEvent("download", (e) => {
|
||||
new Event(e).stop();
|
||||
showDownloadPage();
|
||||
});
|
||||
|
||||
showDownloadPage = function(urls) {
|
||||
const id = 'downloadPage';
|
||||
let contentUri = new URI('download.html');
|
||||
const id = "downloadPage";
|
||||
const contentUri = new URI("download.html");
|
||||
|
||||
if (urls && (urls.length > 0)) {
|
||||
if (urls && (urls.length > 0))
|
||||
contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
|
||||
}
|
||||
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: contentUri.toString(),
|
||||
addClass: 'windowFrame', // fixes iframe scrolling on iOS Safari
|
||||
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
|
||||
scrollbars: true,
|
||||
maximizable: false,
|
||||
closable: true,
|
||||
|
@ -149,19 +147,19 @@ const initializeWindows = function() {
|
|||
updateMainData();
|
||||
};
|
||||
|
||||
addClickEvent('preferences', function(e) {
|
||||
addClickEvent("preferences", (e) => {
|
||||
new Event(e).stop();
|
||||
const id = 'preferencesPage';
|
||||
const id = "preferencesPage";
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
|
||||
loadMethod: 'xhr',
|
||||
loadMethod: "xhr",
|
||||
toolbar: true,
|
||||
contentURL: new URI("views/preferences.html").toString(),
|
||||
require: {
|
||||
css: ['css/Tabs.css']
|
||||
css: ["css/Tabs.css"]
|
||||
},
|
||||
toolbarURL: 'views/preferencesToolbar.html',
|
||||
toolbarURL: "views/preferencesToolbar.html",
|
||||
maximizable: false,
|
||||
closable: true,
|
||||
paddingVertical: 0,
|
||||
|
@ -174,15 +172,15 @@ const initializeWindows = function() {
|
|||
});
|
||||
});
|
||||
|
||||
addClickEvent('upload', function(e) {
|
||||
addClickEvent("upload", (e) => {
|
||||
new Event(e).stop();
|
||||
const id = 'uploadPage';
|
||||
const id = "uploadPage";
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("upload.html").toString(),
|
||||
addClass: 'windowFrame', // fixes iframe scrolling on iOS Safari
|
||||
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
|
||||
scrollbars: true,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
|
@ -198,9 +196,9 @@ const initializeWindows = function() {
|
|||
|
||||
globalUploadLimitFN = function() {
|
||||
new MochaUI.Window({
|
||||
id: 'uploadLimitPage',
|
||||
id: "uploadLimitPage",
|
||||
title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
|
@ -216,9 +214,9 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'uploadLimitPage',
|
||||
id: "uploadLimitPage",
|
||||
title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("uploadlimit.html").setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
|
@ -257,16 +255,16 @@ const initializeWindows = function() {
|
|||
// if all torrents have same share ratio, display that share ratio. else use the default
|
||||
const orig = torrentsHaveSameShareRatio ? shareRatio : "";
|
||||
new MochaUI.Window({
|
||||
id: 'shareRatioPage',
|
||||
id: "shareRatioPage",
|
||||
title: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("shareratio.html").setData("hashes", hashes.join("|")).setData("orig", orig).toString(),
|
||||
scrollbars: false,
|
||||
maximizable: false,
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 0,
|
||||
width: 424,
|
||||
height: 175
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -275,8 +273,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/toggleSequentialDownload',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/toggleSequentialDownload",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -289,8 +287,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/toggleFirstLastPiecePrio',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/toggleFirstLastPiecePrio",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -303,8 +301,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setSuperSeeding',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setSuperSeeding",
|
||||
method: "post",
|
||||
data: {
|
||||
value: val,
|
||||
hashes: hashes.join("|")
|
||||
|
@ -318,10 +316,10 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setForceStart',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setForceStart",
|
||||
method: "post",
|
||||
data: {
|
||||
value: 'true',
|
||||
value: "true",
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
}).send();
|
||||
|
@ -331,9 +329,9 @@ const initializeWindows = function() {
|
|||
|
||||
globalDownloadLimitFN = function() {
|
||||
new MochaUI.Window({
|
||||
id: 'downloadLimitPage',
|
||||
id: "downloadLimitPage",
|
||||
title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
|
@ -346,11 +344,11 @@ const initializeWindows = function() {
|
|||
};
|
||||
|
||||
StatisticsLinkFN = function() {
|
||||
const id = 'statisticspage';
|
||||
const id = "statisticspage";
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: 'QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]',
|
||||
loadMethod: 'xhr',
|
||||
title: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
|
||||
loadMethod: "xhr",
|
||||
contentURL: new URI("views/statistics.html").toString(),
|
||||
maximizable: false,
|
||||
padding: 10,
|
||||
|
@ -366,9 +364,9 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'downloadLimitPage',
|
||||
id: "downloadLimitPage",
|
||||
title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("downloadlimit.html").setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: false,
|
||||
|
@ -385,9 +383,9 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'confirmDeletionPage',
|
||||
id: "confirmDeletionPage",
|
||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).setData("deleteFiles", deleteFiles).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -400,7 +398,7 @@ const initializeWindows = function() {
|
|||
}
|
||||
};
|
||||
|
||||
addClickEvent('delete', function(e) {
|
||||
addClickEvent("delete", (e) => {
|
||||
new Event(e).stop();
|
||||
deleteFN();
|
||||
});
|
||||
|
@ -409,8 +407,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/stop',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/stop",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -423,8 +421,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/start',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/start",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -437,14 +435,14 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
let enable = false;
|
||||
hashes.each(function(hash, index) {
|
||||
hashes.each((hash, index) => {
|
||||
const row = torrentsTable.rows[hash];
|
||||
if (!row.full_data.auto_tmm)
|
||||
enable = true;
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setAutoManagement',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setAutoManagement",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
enable: enable
|
||||
|
@ -458,8 +456,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/recheck',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/recheck",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
}
|
||||
|
@ -472,8 +470,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/reannounce',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/reannounce",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
}
|
||||
|
@ -489,10 +487,10 @@ const initializeWindows = function() {
|
|||
const row = torrentsTable.rows[hash];
|
||||
|
||||
new MochaUI.Window({
|
||||
id: 'setLocationPage',
|
||||
id: "setLocationPage",
|
||||
title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: new URI("setlocation.html").setData("hashes", hashes.join('|')).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("setlocation.html").setData("hashes", hashes.join("|")).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -511,9 +509,9 @@ const initializeWindows = function() {
|
|||
const row = torrentsTable.rows[hash];
|
||||
if (row) {
|
||||
new MochaUI.Window({
|
||||
id: 'renamePage',
|
||||
id: "renamePage",
|
||||
title: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("rename.html").setData("hash", hash).setData("name", row.full_data.name).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -534,11 +532,11 @@ const initializeWindows = function() {
|
|||
const row = torrentsTable.rows[hash];
|
||||
if (row) {
|
||||
new MochaUI.Window({
|
||||
id: 'multiRenamePage',
|
||||
id: "multiRenamePage",
|
||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
data: { hash: hash, selectedRows: [] },
|
||||
loadMethod: 'xhr',
|
||||
contentURL: 'rename_files.html',
|
||||
loadMethod: "xhr",
|
||||
contentURL: "rename_files.html",
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -546,7 +544,7 @@ const initializeWindows = function() {
|
|||
paddingHorizontal: 0,
|
||||
width: 800,
|
||||
height: 420,
|
||||
resizeLimit: { 'x': [800], 'y': [420] }
|
||||
resizeLimit: { "x": [800], "y": [420] }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -557,10 +555,10 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'newCategoryPage',
|
||||
id: "newCategoryPage",
|
||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join('|')).toString(),
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -579,10 +577,10 @@ const initializeWindows = function() {
|
|||
|
||||
const categoryName = category_list.has(categoryHash)
|
||||
? category_list.get(categoryHash).name
|
||||
: '';
|
||||
: "";
|
||||
new Request({
|
||||
url: 'api/v2/torrents/setCategory',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/setCategory",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
category: categoryName
|
||||
|
@ -593,9 +591,9 @@ const initializeWindows = function() {
|
|||
createCategoryFN = function() {
|
||||
const action = "create";
|
||||
new MochaUI.Window({
|
||||
id: 'newCategoryPage',
|
||||
id: "newCategoryPage",
|
||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newcategory.html").setData("action", action).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -612,9 +610,9 @@ const initializeWindows = function() {
|
|||
const action = "createSubcategory";
|
||||
const categoryName = category_list.get(categoryHash).name + "/";
|
||||
new MochaUI.Window({
|
||||
id: 'newSubcategoryPage',
|
||||
id: "newSubcategoryPage",
|
||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -631,10 +629,10 @@ const initializeWindows = function() {
|
|||
const action = "edit";
|
||||
const category = category_list.get(categoryHash);
|
||||
new MochaUI.Window({
|
||||
id: 'editCategoryPage',
|
||||
id: "editCategoryPage",
|
||||
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: new URI('newcategory.html').setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -649,8 +647,8 @@ const initializeWindows = function() {
|
|||
removeCategoryFN = function(categoryHash) {
|
||||
const categoryName = category_list.get(categoryHash).name;
|
||||
new Request({
|
||||
url: 'api/v2/torrents/removeCategories',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/removeCategories",
|
||||
method: "post",
|
||||
data: {
|
||||
categories: categoryName
|
||||
}
|
||||
|
@ -661,26 +659,26 @@ const initializeWindows = function() {
|
|||
deleteUnusedCategoriesFN = function() {
|
||||
const categories = [];
|
||||
category_list.forEach((category, hash) => {
|
||||
if (torrentsTable.getFilteredTorrentsNumber('all', hash, TAGS_ALL, TRACKERS_ALL) === 0)
|
||||
if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0)
|
||||
categories.push(category.name);
|
||||
});
|
||||
|
||||
new Request({
|
||||
url: 'api/v2/torrents/removeCategories',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/removeCategories",
|
||||
method: "post",
|
||||
data: {
|
||||
categories: categories.join('\n')
|
||||
categories: categories.join("\n")
|
||||
}
|
||||
}).send();
|
||||
setCategoryFilter(CATEGORIES_ALL);
|
||||
};
|
||||
|
||||
startTorrentsByCategoryFN = function(categoryHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/start',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/start",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -690,11 +688,11 @@ const initializeWindows = function() {
|
|||
};
|
||||
|
||||
stopTorrentsByCategoryFN = function(categoryHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/stop',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/stop",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -704,12 +702,12 @@ const initializeWindows = function() {
|
|||
};
|
||||
|
||||
deleteTorrentsByCategoryFN = function(categoryHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'confirmDeletionPage',
|
||||
id: "confirmDeletionPage",
|
||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -727,9 +725,9 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'newTagPage',
|
||||
id: "newTagPage",
|
||||
title: "QBT_TR(Add Tags)QBT_TR[CONTEXT=TransferListWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -747,10 +745,10 @@ const initializeWindows = function() {
|
|||
if (hashes.length <= 0)
|
||||
return;
|
||||
|
||||
const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : '';
|
||||
const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : "";
|
||||
new Request({
|
||||
url: (isSet ? 'api/v2/torrents/addTags' : 'api/v2/torrents/removeTags'),
|
||||
method: 'post',
|
||||
url: (isSet ? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"),
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
tags: tagName,
|
||||
|
@ -762,8 +760,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: ('api/v2/torrents/removeTags'),
|
||||
method: 'post',
|
||||
url: ("api/v2/torrents/removeTags"),
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|"),
|
||||
}
|
||||
|
@ -774,9 +772,9 @@ const initializeWindows = function() {
|
|||
createTagFN = function() {
|
||||
const action = "create";
|
||||
new MochaUI.Window({
|
||||
id: 'newTagPage',
|
||||
id: "newTagPage",
|
||||
title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("newtag.html").setData("action", action).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -792,8 +790,8 @@ const initializeWindows = function() {
|
|||
removeTagFN = function(tagHash) {
|
||||
const tagName = tagList.get(tagHash).name;
|
||||
new Request({
|
||||
url: 'api/v2/torrents/deleteTags',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/deleteTags",
|
||||
method: "post",
|
||||
data: {
|
||||
tags: tagName
|
||||
}
|
||||
|
@ -804,25 +802,25 @@ const initializeWindows = function() {
|
|||
deleteUnusedTagsFN = function() {
|
||||
const tags = [];
|
||||
tagList.forEach((tag, hash) => {
|
||||
if (torrentsTable.getFilteredTorrentsNumber('all', CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
|
||||
if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
|
||||
tags.push(tag.name);
|
||||
});
|
||||
new Request({
|
||||
url: 'api/v2/torrents/deleteTags',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/deleteTags",
|
||||
method: "post",
|
||||
data: {
|
||||
tags: tags.join(',')
|
||||
tags: tags.join(",")
|
||||
}
|
||||
}).send();
|
||||
setTagFilter(TAGS_ALL);
|
||||
};
|
||||
|
||||
startTorrentsByTagFN = function(tagHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/start',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/start",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -832,11 +830,11 @@ const initializeWindows = function() {
|
|||
};
|
||||
|
||||
stopTorrentsByTagFN = function(tagHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/stop',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/stop",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -846,12 +844,12 @@ const initializeWindows = function() {
|
|||
};
|
||||
|
||||
deleteTorrentsByTagFN = function(tagHash) {
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'confirmDeletionPage',
|
||||
id: "confirmDeletionPage",
|
||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -869,18 +867,17 @@ const initializeWindows = function() {
|
|||
let hashes = [];
|
||||
switch (trackerHashInt) {
|
||||
case TRACKERS_ALL:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
break;
|
||||
case TRACKERS_TRACKERLESS:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
break;
|
||||
default: {
|
||||
const uniqueTorrents = new Set();
|
||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||
for (const torrent of torrents) {
|
||||
for (const torrent of torrents)
|
||||
uniqueTorrents.add(torrent);
|
||||
}
|
||||
}
|
||||
hashes = [...uniqueTorrents];
|
||||
break;
|
||||
}
|
||||
|
@ -888,8 +885,8 @@ const initializeWindows = function() {
|
|||
|
||||
if (hashes.length > 0) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/start',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/start",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -903,18 +900,17 @@ const initializeWindows = function() {
|
|||
let hashes = [];
|
||||
switch (trackerHashInt) {
|
||||
case TRACKERS_ALL:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
break;
|
||||
case TRACKERS_TRACKERLESS:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
break;
|
||||
default: {
|
||||
const uniqueTorrents = new Set();
|
||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||
for (const torrent of torrents) {
|
||||
for (const torrent of torrents)
|
||||
uniqueTorrents.add(torrent);
|
||||
}
|
||||
}
|
||||
hashes = [...uniqueTorrents];
|
||||
break;
|
||||
}
|
||||
|
@ -922,8 +918,8 @@ const initializeWindows = function() {
|
|||
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/stop',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/stop",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -937,18 +933,17 @@ const initializeWindows = function() {
|
|||
let hashes = [];
|
||||
switch (trackerHashInt) {
|
||||
case TRACKERS_ALL:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||
break;
|
||||
case TRACKERS_TRACKERLESS:
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||
break;
|
||||
default: {
|
||||
const uniqueTorrents = new Set();
|
||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||
for (const torrent of torrents) {
|
||||
for (const torrent of torrents)
|
||||
uniqueTorrents.add(torrent);
|
||||
}
|
||||
}
|
||||
hashes = [...uniqueTorrents];
|
||||
break;
|
||||
}
|
||||
|
@ -956,9 +951,9 @@ const initializeWindows = function() {
|
|||
|
||||
if (hashes.length) {
|
||||
new MochaUI.Window({
|
||||
id: 'confirmDeletionPage',
|
||||
id: "confirmDeletionPage",
|
||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||
loadMethod: 'iframe',
|
||||
loadMethod: "iframe",
|
||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
|
@ -1068,13 +1063,13 @@ const initializeWindows = function() {
|
|||
}
|
||||
};
|
||||
|
||||
addClickEvent('stopAll', (e) => {
|
||||
addClickEvent("stopAll", (e) => {
|
||||
new Event(e).stop();
|
||||
|
||||
if (confirm('QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]')) {
|
||||
if (confirm("QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/stop',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/stop",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: "all"
|
||||
}
|
||||
|
@ -1083,13 +1078,13 @@ const initializeWindows = function() {
|
|||
}
|
||||
});
|
||||
|
||||
addClickEvent('startAll', (e) => {
|
||||
addClickEvent("startAll", (e) => {
|
||||
new Event(e).stop();
|
||||
|
||||
if (confirm('QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]')) {
|
||||
if (confirm("QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/start',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/start",
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: "all"
|
||||
}
|
||||
|
@ -1098,15 +1093,15 @@ const initializeWindows = function() {
|
|||
}
|
||||
});
|
||||
|
||||
['stop', 'start', 'recheck'].each(function(item) {
|
||||
addClickEvent(item, function(e) {
|
||||
["stop", "start", "recheck"].each((item) => {
|
||||
addClickEvent(item, (e) => {
|
||||
new Event(e).stop();
|
||||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
hashes.each(function(hash, index) {
|
||||
hashes.each((hash, index) => {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/' + item,
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/" + item,
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hash
|
||||
}
|
||||
|
@ -1117,8 +1112,8 @@ const initializeWindows = function() {
|
|||
});
|
||||
});
|
||||
|
||||
['decreasePrio', 'increasePrio', 'topPrio', 'bottomPrio'].each(function(item) {
|
||||
addClickEvent(item, function(e) {
|
||||
["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item) => {
|
||||
addClickEvent(item, (e) => {
|
||||
new Event(e).stop();
|
||||
setQueuePositionFN(item);
|
||||
});
|
||||
|
@ -1128,8 +1123,8 @@ const initializeWindows = function() {
|
|||
const hashes = torrentsTable.selectedRowsIds();
|
||||
if (hashes.length) {
|
||||
new Request({
|
||||
url: 'api/v2/torrents/' + cmd,
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/" + cmd,
|
||||
method: "post",
|
||||
data: {
|
||||
hashes: hashes.join("|")
|
||||
}
|
||||
|
@ -1138,19 +1133,19 @@ const initializeWindows = function() {
|
|||
}
|
||||
};
|
||||
|
||||
addClickEvent('about', function(e) {
|
||||
addClickEvent("about", (e) => {
|
||||
new Event(e).stop();
|
||||
const id = 'aboutpage';
|
||||
const id = "aboutpage";
|
||||
new MochaUI.Window({
|
||||
id: id,
|
||||
title: 'QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]',
|
||||
loadMethod: 'xhr',
|
||||
title: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
|
||||
loadMethod: "xhr",
|
||||
contentURL: new URI("views/about.html").toString(),
|
||||
require: {
|
||||
css: ['css/Tabs.css']
|
||||
css: ["css/Tabs.css"]
|
||||
},
|
||||
toolbar: true,
|
||||
toolbarURL: 'views/aboutToolbar.html',
|
||||
toolbarURL: "views/aboutToolbar.html",
|
||||
padding: 10,
|
||||
width: loadWindowWidth(id, 550),
|
||||
height: loadWindowHeight(id, 360),
|
||||
|
@ -1160,25 +1155,25 @@ const initializeWindows = function() {
|
|||
});
|
||||
});
|
||||
|
||||
addClickEvent('logout', function(e) {
|
||||
addClickEvent("logout", (e) => {
|
||||
new Event(e).stop();
|
||||
new Request({
|
||||
url: 'api/v2/auth/logout',
|
||||
method: 'post',
|
||||
url: "api/v2/auth/logout",
|
||||
method: "post",
|
||||
onSuccess: function() {
|
||||
window.location.reload(true);
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
|
||||
addClickEvent('shutdown', function(e) {
|
||||
addClickEvent("shutdown", (e) => {
|
||||
new Event(e).stop();
|
||||
if (confirm('QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]')) {
|
||||
if (confirm("QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]")) {
|
||||
new Request({
|
||||
url: 'api/v2/app/shutdown',
|
||||
method: 'post',
|
||||
url: "api/v2/app/shutdown",
|
||||
method: "post",
|
||||
onSuccess: function() {
|
||||
const shutdownMessage = 'QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]'.replace("%1", window.qBittorrent.Client.mainTitle());
|
||||
const shutdownMessage = "QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Client.mainTitle());
|
||||
document.write(`<!doctype html><html lang="${LANG}"><head> <meta charset="UTF-8"> <meta name="color-scheme" content="light dark"> <title>${shutdownMessage}</title> <style>* {font-family: Arial, Helvetica, sans-serif;}</style></head><body> <h1 style="text-align: center;">${shutdownMessage}</h1></body></html>`);
|
||||
document.close();
|
||||
window.stop();
|
||||
|
@ -1189,8 +1184,8 @@ const initializeWindows = function() {
|
|||
});
|
||||
|
||||
// Deactivate menu header links
|
||||
$$('a.returnFalse').each(function(el) {
|
||||
el.addEvent('click', function(e) {
|
||||
$$("a.returnFalse").each((el) => {
|
||||
el.addEvent("click", (e) => {
|
||||
new Event(e).stop();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.PiecesBar = (() => {
|
||||
const exports = () => {
|
||||
|
@ -51,35 +50,35 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
const PiecesBar = new Class({
|
||||
initialize(pieces, parameters) {
|
||||
const vals = {
|
||||
'id': 'piecesbar_' + (piecesBarUniqueId++),
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'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)'
|
||||
"id": "piecesbar_" + (piecesBarUniqueId++),
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"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)"
|
||||
};
|
||||
|
||||
if (parameters && (typeOf(parameters) === 'object'))
|
||||
if (parameters && (typeOf(parameters) === "object"))
|
||||
Object.append(vals, parameters);
|
||||
vals.height = Math.max(vals.height, 12);
|
||||
|
||||
const obj = new Element('div', {
|
||||
'id': vals.id,
|
||||
'class': 'piecesbarWrapper',
|
||||
'styles': {
|
||||
'border': vals.borderSize.toString() + 'px solid ' + vals.borderColor,
|
||||
'height': vals.height.toString() + 'px',
|
||||
const obj = new Element("div", {
|
||||
"id": vals.id,
|
||||
"class": "piecesbarWrapper",
|
||||
"styles": {
|
||||
"border": vals.borderSize.toString() + "px solid " + vals.borderColor,
|
||||
"height": vals.height.toString() + "px",
|
||||
}
|
||||
});
|
||||
obj.vals = vals;
|
||||
obj.vals.pieces = [pieces, []].pick();
|
||||
|
||||
obj.vals.canvas = new Element('canvas', {
|
||||
'id': vals.id + '_canvas',
|
||||
'class': 'piecesbarCanvas',
|
||||
'width': (vals.width - (2 * vals.borderSize)).toString(),
|
||||
'height': '1' // will stretch vertically to take up the height of the parent
|
||||
obj.vals.canvas = new Element("canvas", {
|
||||
"id": vals.id + "_canvas",
|
||||
"class": "piecesbarCanvas",
|
||||
"width": (vals.width - (2 * vals.borderSize)).toString(),
|
||||
"height": "1" // will stretch vertically to take up the height of the parent
|
||||
});
|
||||
obj.appendChild(obj.vals.canvas);
|
||||
|
||||
|
@ -124,7 +123,7 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
this.vals.canvas.width = width - (2 * this.vals.borderSize);
|
||||
|
||||
const canvas = this.vals.canvas;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const imageWidth = canvas.width;
|
||||
|
@ -136,14 +135,11 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
let maxStatus = 0;
|
||||
|
||||
for (const status of pieces) {
|
||||
if (status > maxStatus) {
|
||||
if (status > maxStatus)
|
||||
maxStatus = status;
|
||||
}
|
||||
|
||||
if (status < minStatus) {
|
||||
if (status < minStatus)
|
||||
minStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
// if no progress then don't do anything
|
||||
if (maxStatus === 0)
|
||||
|
@ -220,15 +216,13 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
statusValues[STATUS_DOWNLOADING] = Math.min(statusValues[STATUS_DOWNLOADING], 1);
|
||||
statusValues[STATUS_DOWNLOADED] = Math.min(statusValues[STATUS_DOWNLOADED], 1);
|
||||
|
||||
if (!lastValue) {
|
||||
if (!lastValue)
|
||||
lastValue = statusValues;
|
||||
}
|
||||
|
||||
// group contiguous colors together and draw as a single rectangle
|
||||
if ((lastValue[STATUS_DOWNLOADING] === statusValues[STATUS_DOWNLOADING])
|
||||
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED])) {
|
||||
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED]))
|
||||
continue;
|
||||
}
|
||||
|
||||
const rectangleWidth = x - rectangleStart;
|
||||
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||
|
@ -246,7 +240,7 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
|
||||
function drawStatus(ctx, start, width, statusValues) {
|
||||
// mix the colors by using transparency and a composite mode
|
||||
ctx.globalCompositeOperation = 'lighten';
|
||||
ctx.globalCompositeOperation = "lighten";
|
||||
|
||||
if (statusValues[STATUS_DOWNLOADING]) {
|
||||
ctx.globalAlpha = statusValues[STATUS_DOWNLOADING];
|
||||
|
@ -266,7 +260,7 @@ window.qBittorrent.PiecesBar = (() => {
|
|||
if (!obj)
|
||||
return;
|
||||
if (!obj.parentNode)
|
||||
return setTimeout(function() { checkForParent(id); }, 1);
|
||||
return setTimeout(() => { checkForParent(id); }, 1);
|
||||
|
||||
obj.refresh();
|
||||
}
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.ProgressBar = (function() {
|
||||
const exports = function() {
|
||||
|
@ -43,60 +42,60 @@ window.qBittorrent.ProgressBar = (function() {
|
|||
const ProgressBar = new Class({
|
||||
initialize: function(value, parameters) {
|
||||
const vals = {
|
||||
'id': 'progressbar_' + (ProgressBars++),
|
||||
'value': [value, 0].pick(),
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'darkbg': 'var(--color-background-blue)',
|
||||
'darkfg': 'var(--color-text-white)',
|
||||
'lightbg': 'var(--color-background-default)',
|
||||
'lightfg': 'var(--color-text-default)'
|
||||
"id": "progressbar_" + (ProgressBars++),
|
||||
"value": [value, 0].pick(),
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"darkbg": "var(--color-background-blue)",
|
||||
"darkfg": "var(--color-text-white)",
|
||||
"lightbg": "var(--color-background-default)",
|
||||
"lightfg": "var(--color-text-default)"
|
||||
};
|
||||
if (parameters && (typeOf(parameters) === 'object'))
|
||||
if (parameters && (typeOf(parameters) === "object"))
|
||||
Object.append(vals, parameters);
|
||||
if (vals.height < 12)
|
||||
vals.height = 12;
|
||||
const obj = new Element('div', {
|
||||
'id': vals.id,
|
||||
'class': 'progressbar_wrapper',
|
||||
'styles': {
|
||||
'border': '1px solid var(--color-border-default)',
|
||||
'width': vals.width,
|
||||
'height': vals.height,
|
||||
'position': 'relative',
|
||||
'margin': '0 auto'
|
||||
const obj = new Element("div", {
|
||||
"id": vals.id,
|
||||
"class": "progressbar_wrapper",
|
||||
"styles": {
|
||||
"border": "1px solid var(--color-border-default)",
|
||||
"width": vals.width,
|
||||
"height": vals.height,
|
||||
"position": "relative",
|
||||
"margin": "0 auto"
|
||||
}
|
||||
});
|
||||
obj.vals = vals;
|
||||
obj.vals.value = [value, 0].pick();
|
||||
obj.vals.dark = new Element('div', {
|
||||
'id': vals.id + '_dark',
|
||||
'class': 'progressbar_dark',
|
||||
'styles': {
|
||||
'width': vals.width,
|
||||
'height': vals.height,
|
||||
'background': vals.darkbg,
|
||||
'color': vals.darkfg,
|
||||
'position': 'absolute',
|
||||
'text-align': 'center',
|
||||
'left': 0,
|
||||
'top': 0,
|
||||
'line-height': vals.height
|
||||
obj.vals.dark = new Element("div", {
|
||||
"id": vals.id + "_dark",
|
||||
"class": "progressbar_dark",
|
||||
"styles": {
|
||||
"width": vals.width,
|
||||
"height": vals.height,
|
||||
"background": vals.darkbg,
|
||||
"color": vals.darkfg,
|
||||
"position": "absolute",
|
||||
"text-align": "center",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"line-height": vals.height
|
||||
}
|
||||
});
|
||||
obj.vals.light = new Element('div', {
|
||||
'id': vals.id + '_light',
|
||||
'class': 'progressbar_light',
|
||||
'styles': {
|
||||
'width': vals.width,
|
||||
'height': vals.height,
|
||||
'background': vals.lightbg,
|
||||
'color': vals.lightfg,
|
||||
'position': 'absolute',
|
||||
'text-align': 'center',
|
||||
'left': 0,
|
||||
'top': 0,
|
||||
'line-height': vals.height
|
||||
obj.vals.light = new Element("div", {
|
||||
"id": vals.id + "_light",
|
||||
"class": "progressbar_light",
|
||||
"styles": {
|
||||
"width": vals.width,
|
||||
"height": vals.height,
|
||||
"background": vals.lightbg,
|
||||
"color": vals.lightfg,
|
||||
"position": "absolute",
|
||||
"text-align": "center",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"line-height": vals.height
|
||||
}
|
||||
});
|
||||
obj.appendChild(obj.vals.dark);
|
||||
|
@ -120,26 +119,24 @@ window.qBittorrent.ProgressBar = (function() {
|
|||
value = parseFloat(value);
|
||||
if (isNaN(value))
|
||||
value = 0;
|
||||
if (value > 100)
|
||||
value = 100;
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
value = Math.min(Math.max(value, 0), 100);
|
||||
this.vals.value = value;
|
||||
this.vals.dark.empty();
|
||||
this.vals.light.empty();
|
||||
this.vals.dark.appendText(value.round(1).toFixed(1) + '%');
|
||||
this.vals.light.appendText(value.round(1).toFixed(1) + '%');
|
||||
const r = parseInt(this.vals.width * (value / 100));
|
||||
this.vals.dark.setStyle('clip', 'rect(0,' + r + 'px,' + this.vals.height + 'px,0)');
|
||||
this.vals.light.setStyle('clip', 'rect(0,' + this.vals.width + 'px,' + this.vals.height + 'px,' + r + 'px)');
|
||||
|
||||
const displayedValue = `${value.round(1).toFixed(1)}%`;
|
||||
this.vals.dark.textContent = displayedValue;
|
||||
this.vals.light.textContent = displayedValue;
|
||||
|
||||
const r = parseInt((this.vals.width * (value / 100)), 10);
|
||||
this.vals.dark.setStyle("clip", `rect(0, ${r}px, ${this.vals.height}px, 0)`);
|
||||
this.vals.light.setStyle("clip", `rect(0, ${this.vals.width}px, ${this.vals.height}px, ${r}px)`);
|
||||
}
|
||||
|
||||
function ProgressBar_setWidth(value) {
|
||||
if (this.vals.width !== value) {
|
||||
this.vals.width = value;
|
||||
this.setStyle('width', value);
|
||||
this.vals.dark.setStyle('width', value);
|
||||
this.vals.light.setStyle('width', value);
|
||||
this.setStyle("width", value);
|
||||
this.vals.dark.setStyle("width", value);
|
||||
this.vals.light.setStyle("width", value);
|
||||
this.setValue(this.vals.value);
|
||||
}
|
||||
}
|
||||
|
@ -150,10 +147,10 @@ window.qBittorrent.ProgressBar = (function() {
|
|||
return;
|
||||
if (!obj.parentNode)
|
||||
return setTimeout('ProgressBar_checkForParent("' + id + '")', 1);
|
||||
obj.setStyle('width', '100%');
|
||||
obj.setStyle("width", "100%");
|
||||
const w = obj.offsetWidth;
|
||||
obj.vals.dark.setStyle('width', w);
|
||||
obj.vals.light.setStyle('width', w);
|
||||
obj.vals.dark.setStyle("width", w);
|
||||
obj.vals.light.setStyle("width", w);
|
||||
obj.vals.width = w;
|
||||
obj.setValue(obj.vals.value);
|
||||
}
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.PropFiles = (function() {
|
||||
const exports = function() {
|
||||
|
@ -82,7 +81,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const getChildFiles = function(node) {
|
||||
if (node.isFolder) {
|
||||
node.children.each(function(child) {
|
||||
node.children.each((child) => {
|
||||
getChildFiles(child);
|
||||
});
|
||||
}
|
||||
|
@ -92,7 +91,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
node.children.each(function(child) {
|
||||
node.children.each((child) => {
|
||||
getChildFiles(child);
|
||||
});
|
||||
|
||||
|
@ -107,8 +106,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const checkbox = e.target;
|
||||
const priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored;
|
||||
const id = checkbox.get('data-id');
|
||||
const fileId = checkbox.get('data-file-id');
|
||||
const id = checkbox.get("data-id");
|
||||
const fileId = checkbox.get("data-file-id");
|
||||
|
||||
const rows = getAllChildren(id, fileId);
|
||||
|
||||
|
@ -119,8 +118,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const fileComboboxChanged = function(e) {
|
||||
const combobox = e.target;
|
||||
const priority = combobox.value;
|
||||
const id = combobox.get('data-id');
|
||||
const fileId = combobox.get('data-file-id');
|
||||
const id = combobox.get("data-id");
|
||||
const fileId = combobox.get("data-file-id");
|
||||
|
||||
const rows = getAllChildren(id, fileId);
|
||||
|
||||
|
@ -129,24 +128,24 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const isDownloadCheckboxExists = function(id) {
|
||||
return ($('cbPrio' + id) !== null);
|
||||
return ($("cbPrio" + id) !== null);
|
||||
};
|
||||
|
||||
const createDownloadCheckbox = function(id, fileId, checked) {
|
||||
const checkbox = new Element('input');
|
||||
checkbox.set('type', 'checkbox');
|
||||
checkbox.set('id', 'cbPrio' + id);
|
||||
checkbox.set('data-id', id);
|
||||
checkbox.set('data-file-id', fileId);
|
||||
checkbox.set('class', 'DownloadedCB');
|
||||
checkbox.addEvent('click', fileCheckboxClicked);
|
||||
const checkbox = new Element("input");
|
||||
checkbox.set("type", "checkbox");
|
||||
checkbox.set("id", "cbPrio" + id);
|
||||
checkbox.set("data-id", id);
|
||||
checkbox.set("data-file-id", fileId);
|
||||
checkbox.set("class", "DownloadedCB");
|
||||
checkbox.addEvent("click", fileCheckboxClicked);
|
||||
|
||||
updateCheckbox(checkbox, checked);
|
||||
return checkbox;
|
||||
};
|
||||
|
||||
const updateDownloadCheckbox = function(id, checked) {
|
||||
const checkbox = $('cbPrio' + id);
|
||||
const checkbox = $("cbPrio" + id);
|
||||
updateCheckbox(checkbox, checked);
|
||||
};
|
||||
|
||||
|
@ -165,43 +164,42 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const isPriorityComboExists = function(id) {
|
||||
return ($('comboPrio' + id) !== null);
|
||||
return ($("comboPrio" + id) !== null);
|
||||
};
|
||||
|
||||
const createPriorityOptionElement = function(priority, selected, html) {
|
||||
const elem = new Element('option');
|
||||
elem.set('value', priority.toString());
|
||||
elem.set('html', html);
|
||||
const elem = new Element("option");
|
||||
elem.set("value", priority.toString());
|
||||
elem.set("html", html);
|
||||
if (selected)
|
||||
elem.selected = true;
|
||||
return elem;
|
||||
};
|
||||
|
||||
const createPriorityCombo = function(id, fileId, selectedPriority) {
|
||||
const select = new Element('select');
|
||||
select.set('id', 'comboPrio' + id);
|
||||
select.set('data-id', id);
|
||||
select.set('data-file-id', fileId);
|
||||
select.addClass('combo_priority');
|
||||
select.addEvent('change', fileComboboxChanged);
|
||||
const select = new Element("select");
|
||||
select.set("id", "comboPrio" + id);
|
||||
select.set("data-id", id);
|
||||
select.set("data-file-id", fileId);
|
||||
select.addClass("combo_priority");
|
||||
select.addEvent("change", fileComboboxChanged);
|
||||
|
||||
createPriorityOptionElement(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), 'QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.Normal, (FilePriority.Normal === selectedPriority), 'QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.High, (FilePriority.High === selectedPriority), 'QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.Maximum, (FilePriority.Maximum === selectedPriority), 'QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), "QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.Normal, (FilePriority.Normal === selectedPriority), "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.High, (FilePriority.High === selectedPriority), "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
|
||||
createPriorityOptionElement(FilePriority.Maximum, (FilePriority.Maximum === selectedPriority), "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
|
||||
|
||||
// "Mixed" priority is for display only; it shouldn't be selectable
|
||||
const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), 'QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]');
|
||||
mixedPriorityOption.set('disabled', true);
|
||||
const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), "QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]");
|
||||
mixedPriorityOption.set("disabled", true);
|
||||
mixedPriorityOption.injectInside(select);
|
||||
|
||||
return select;
|
||||
};
|
||||
|
||||
const updatePriorityCombo = function(id, selectedPriority) {
|
||||
const combobox = $('comboPrio' + id);
|
||||
|
||||
if (parseInt(combobox.value) !== selectedPriority)
|
||||
const combobox = $("comboPrio" + id);
|
||||
if (parseInt(combobox.value, 10) !== selectedPriority)
|
||||
selectComboboxPriority(combobox, selectedPriority);
|
||||
};
|
||||
|
||||
|
@ -209,7 +207,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const options = combobox.options;
|
||||
for (let i = 0; i < options.length; ++i) {
|
||||
const option = options[i];
|
||||
if (parseInt(option.value) === priority)
|
||||
if (parseInt(option.value, 10) === priority)
|
||||
option.selected = true;
|
||||
else
|
||||
option.selected = false;
|
||||
|
@ -224,12 +222,12 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const rowIds = [];
|
||||
const fileIds = [];
|
||||
let priority = FilePriority.Ignored;
|
||||
const checkbox = $('tristate_cb');
|
||||
const checkbox = $("tristate_cb");
|
||||
|
||||
if (checkbox.state === "checked") {
|
||||
setCheckboxUnchecked(checkbox);
|
||||
// set file priority for all checked to Ignored
|
||||
torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) {
|
||||
torrentFilesTable.getFilteredAndSortedRows().forEach((row) => {
|
||||
const rowId = row.rowId;
|
||||
const fileId = row.full_data.fileId;
|
||||
const isChecked = (row.full_data.checked === TriState.Checked);
|
||||
|
@ -244,7 +242,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
setCheckboxChecked(checkbox);
|
||||
priority = FilePriority.Normal;
|
||||
// set file priority for all unchecked to Normal
|
||||
torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) {
|
||||
torrentFilesTable.getFilteredAndSortedRows().forEach((row) => {
|
||||
const rowId = row.rowId;
|
||||
const fileId = row.full_data.fileId;
|
||||
const isUnchecked = (row.full_data.checked === TriState.Unchecked);
|
||||
|
@ -261,7 +259,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const updateGlobalCheckbox = function() {
|
||||
const checkbox = $('tristate_cb');
|
||||
const checkbox = $("tristate_cb");
|
||||
if (isAllCheckboxesChecked())
|
||||
setCheckboxChecked(checkbox);
|
||||
else if (isAllCheckboxesUnchecked())
|
||||
|
@ -288,7 +286,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const isAllCheckboxesChecked = function() {
|
||||
const checkboxes = $$('input.DownloadedCB');
|
||||
const checkboxes = $$("input.DownloadedCB");
|
||||
for (let i = 0; i < checkboxes.length; ++i) {
|
||||
if (!checkboxes[i].checked)
|
||||
return false;
|
||||
|
@ -297,7 +295,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const isAllCheckboxesUnchecked = function() {
|
||||
const checkboxes = $$('input.DownloadedCB');
|
||||
const checkboxes = $$("input.DownloadedCB");
|
||||
for (let i = 0; i < checkboxes.length; ++i) {
|
||||
if (checkboxes[i].checked)
|
||||
return false;
|
||||
|
@ -311,12 +309,12 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
clearTimeout(loadTorrentFilesDataTimer);
|
||||
new Request({
|
||||
url: 'api/v2/torrents/filePrio',
|
||||
method: 'post',
|
||||
url: "api/v2/torrents/filePrio",
|
||||
method: "post",
|
||||
data: {
|
||||
'hash': current_hash,
|
||||
'id': fileIds.join('|'),
|
||||
'priority': priority
|
||||
"hash": current_hash,
|
||||
"id": fileIds.join("|"),
|
||||
"priority": priority
|
||||
},
|
||||
onComplete: function() {
|
||||
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000);
|
||||
|
@ -324,10 +322,10 @@ window.qBittorrent.PropFiles = (function() {
|
|||
}).send();
|
||||
|
||||
const ignore = (priority === FilePriority.Ignored);
|
||||
ids.forEach(function(_id) {
|
||||
ids.forEach((_id) => {
|
||||
torrentFilesTable.setIgnored(_id, ignore);
|
||||
|
||||
const combobox = $('comboPrio' + _id);
|
||||
const combobox = $("comboPrio" + _id);
|
||||
if (combobox !== null)
|
||||
selectComboboxPriority(combobox, priority);
|
||||
});
|
||||
|
@ -337,8 +335,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
let loadTorrentFilesDataTimer;
|
||||
const loadTorrentFilesData = function() {
|
||||
if ($('prop_files').hasClass('invisible')
|
||||
|| $('propertiesPanel_collapseToggle').hasClass('panel-expand')) {
|
||||
if ($("prop_files").hasClass("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").hasClass("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
@ -355,10 +353,10 @@ window.qBittorrent.PropFiles = (function() {
|
|||
current_hash = new_hash;
|
||||
loadedNewTorrent = true;
|
||||
}
|
||||
const url = new URI('api/v2/torrents/files?hash=' + current_hash);
|
||||
const url = new URI("api/v2/torrents/files?hash=" + current_hash);
|
||||
new Request.JSON({
|
||||
url: url,
|
||||
method: 'get',
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onComplete: function() {
|
||||
clearTimeout(loadTorrentFilesDataTimer);
|
||||
|
@ -388,7 +386,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const handleNewTorrentFiles = function(files) {
|
||||
is_seed = (files.length > 0) ? files[0].is_seed : true;
|
||||
|
||||
const rows = files.map(function(file, index) {
|
||||
const rows = files.map((file, index) => {
|
||||
let progress = (file.progress * 100).round(1);
|
||||
if ((progress === 100) && (file.progress < 1))
|
||||
progress = 99.9;
|
||||
|
@ -421,13 +419,13 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
||||
|
||||
rows.forEach(function(row) {
|
||||
rows.forEach((row) => {
|
||||
const pathItems = row.fileName.split(window.qBittorrent.Filesystem.PathSeparator);
|
||||
|
||||
pathItems.pop(); // remove last item (i.e. file name)
|
||||
let parent = rootNode;
|
||||
pathItems.forEach(function(folderName) {
|
||||
if (folderName === '.unwanted')
|
||||
pathItems.forEach((folderName) => {
|
||||
if (folderName === ".unwanted")
|
||||
return;
|
||||
|
||||
let folderNode = null;
|
||||
|
@ -474,7 +472,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
parent.addChild(childNode);
|
||||
|
||||
++rowId;
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
torrentFilesTable.populateTable(rootNode);
|
||||
torrentFilesTable.updateTable(false);
|
||||
|
@ -497,16 +495,14 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const expandFolder = function(id) {
|
||||
const node = torrentFilesTable.getNode(id);
|
||||
if (node.isFolder) {
|
||||
if (node.isFolder)
|
||||
expandNode(node);
|
||||
}
|
||||
};
|
||||
|
||||
const collapseFolder = function(id) {
|
||||
const node = torrentFilesTable.getNode(id);
|
||||
if (node.isFolder) {
|
||||
if (node.isFolder)
|
||||
collapseNode(node);
|
||||
}
|
||||
};
|
||||
|
||||
const filesPriorityMenuClicked = function(priority) {
|
||||
|
@ -516,8 +512,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const rowIds = [];
|
||||
const fileIds = [];
|
||||
selectedRows.forEach(function(rowId) {
|
||||
const elem = $('comboPrio' + rowId);
|
||||
selectedRows.forEach((rowId) => {
|
||||
const elem = $("comboPrio" + rowId);
|
||||
rowIds.push(rowId);
|
||||
fileIds.push(elem.get("data-file-id"));
|
||||
});
|
||||
|
@ -526,10 +522,10 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const uniqueFileIds = {};
|
||||
for (let i = 0; i < rowIds.length; ++i) {
|
||||
const rows = getAllChildren(rowIds[i], fileIds[i]);
|
||||
rows.rowIds.forEach(function(rowId) {
|
||||
rows.rowIds.forEach((rowId) => {
|
||||
uniqueRowIds[rowId] = true;
|
||||
});
|
||||
rows.fileIds.forEach(function(fileId) {
|
||||
rows.fileIds.forEach((fileId) => {
|
||||
uniqueFileIds[fileId] = true;
|
||||
});
|
||||
}
|
||||
|
@ -549,11 +545,11 @@ window.qBittorrent.PropFiles = (function() {
|
|||
const path = node.path;
|
||||
|
||||
new MochaUI.Window({
|
||||
id: 'renamePage',
|
||||
id: "renamePage",
|
||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
||||
loadMethod: 'iframe',
|
||||
contentURL: 'rename_file.html?hash=' + hash + '&isFolder=' + node.isFolder
|
||||
+ '&path=' + encodeURIComponent(path),
|
||||
loadMethod: "iframe",
|
||||
contentURL: "rename_file.html?hash=" + hash + "&isFolder=" + node.isFolder
|
||||
+ "&path=" + encodeURIComponent(path),
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -566,11 +562,11 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const multiFileRename = function(hash) {
|
||||
new MochaUI.Window({
|
||||
id: 'multiRenamePage',
|
||||
id: "multiRenamePage",
|
||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
||||
data: { hash: hash, selectedRows: torrentFilesTable.selectedRows },
|
||||
loadMethod: 'xhr',
|
||||
contentURL: 'rename_files.html',
|
||||
loadMethod: "xhr",
|
||||
contentURL: "rename_files.html",
|
||||
scrollbars: false,
|
||||
resizable: true,
|
||||
maximizable: false,
|
||||
|
@ -578,25 +574,23 @@ window.qBittorrent.PropFiles = (function() {
|
|||
paddingHorizontal: 0,
|
||||
width: 800,
|
||||
height: 420,
|
||||
resizeLimit: { 'x': [800], 'y': [420] }
|
||||
resizeLimit: { "x": [800], "y": [420] }
|
||||
});
|
||||
};
|
||||
|
||||
const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||
targets: '#torrentFilesTableDiv tr',
|
||||
menu: 'torrentFilesMenu',
|
||||
targets: "#torrentFilesTableDiv tr",
|
||||
menu: "torrentFilesMenu",
|
||||
actions: {
|
||||
Rename: function(element, ref) {
|
||||
const hash = torrentsTable.getCurrentTorrentID();
|
||||
if (!hash)
|
||||
return;
|
||||
|
||||
if (torrentFilesTable.selectedRowsIds().length > 1) {
|
||||
if (torrentFilesTable.selectedRowsIds().length > 1)
|
||||
multiFileRename(hash);
|
||||
}
|
||||
else {
|
||||
else
|
||||
singleFileRename(hash);
|
||||
}
|
||||
},
|
||||
|
||||
FilePrioIgnore: function(element, ref) {
|
||||
|
@ -618,20 +612,20 @@ window.qBittorrent.PropFiles = (function() {
|
|||
},
|
||||
onShow: function() {
|
||||
if (is_seed)
|
||||
this.hideItem('FilePrio');
|
||||
this.hideItem("FilePrio");
|
||||
else
|
||||
this.showItem('FilePrio');
|
||||
this.showItem("FilePrio");
|
||||
}
|
||||
});
|
||||
|
||||
torrentFilesTable.setup('torrentFilesTableDiv', 'torrentFilesTableFixedHeaderDiv', torrentFilesContextMenu);
|
||||
torrentFilesTable.setup("torrentFilesTableDiv", "torrentFilesTableFixedHeaderDiv", torrentFilesContextMenu);
|
||||
// inject checkbox into table header
|
||||
const tableHeaders = $$('#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th');
|
||||
const tableHeaders = $$("#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||
if (tableHeaders.length > 0) {
|
||||
const checkbox = new Element('input');
|
||||
checkbox.set('type', 'checkbox');
|
||||
checkbox.set('id', 'tristate_cb');
|
||||
checkbox.addEvent('click', switchCheckboxState);
|
||||
const checkbox = new Element("input");
|
||||
checkbox.set("type", "checkbox");
|
||||
checkbox.set("id", "tristate_cb");
|
||||
checkbox.addEvent("click", switchCheckboxState);
|
||||
|
||||
const checkboxTH = tableHeaders[0];
|
||||
checkbox.injectInside(checkboxTH);
|
||||
|
@ -639,14 +633,14 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
// default sort by name column
|
||||
if (torrentFilesTable.getSortedColumn() === null)
|
||||
torrentFilesTable.setSortedColumn('name');
|
||||
torrentFilesTable.setSortedColumn("name");
|
||||
|
||||
// listen for changes to torrentFilesFilterInput
|
||||
let torrentFilesFilterInputTimer = -1;
|
||||
$('torrentFilesFilterInput').addEvent('input', () => {
|
||||
$("torrentFilesFilterInput").addEvent("input", () => {
|
||||
clearTimeout(torrentFilesFilterInputTimer);
|
||||
|
||||
const value = $('torrentFilesFilterInput').get("value");
|
||||
const value = $("torrentFilesFilterInput").get("value");
|
||||
torrentFilesTable.setFilter(value);
|
||||
|
||||
torrentFilesFilterInputTimer = setTimeout(() => {
|
||||
|
@ -668,7 +662,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
* Show/hide a node's row
|
||||
*/
|
||||
const _hideNode = function(node, shouldHide) {
|
||||
const span = $('filesTablefileName' + node.rowId);
|
||||
const span = $("filesTablefileName" + node.rowId);
|
||||
// span won't exist if row has been filtered out
|
||||
if (span === null)
|
||||
return;
|
||||
|
@ -683,7 +677,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
* Update a node's collapsed state and icon
|
||||
*/
|
||||
const _updateNodeState = function(node, isCollapsed) {
|
||||
const span = $('filesTablefileName' + node.rowId);
|
||||
const span = $("filesTablefileName" + node.rowId);
|
||||
// span won't exist if row has been filtered out
|
||||
if (span === null)
|
||||
return;
|
||||
|
@ -701,7 +695,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
};
|
||||
|
||||
const _isCollapsed = function(node) {
|
||||
const span = $('filesTablefileName' + node.rowId);
|
||||
const span = $("filesTablefileName" + node.rowId);
|
||||
if (span === null)
|
||||
return true;
|
||||
|
||||
|
@ -721,8 +715,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const expandAllNodes = function() {
|
||||
const root = torrentFilesTable.getRoot();
|
||||
root.children.each(function(node) {
|
||||
node.children.each(function(child) {
|
||||
root.children.each((node) => {
|
||||
node.children.each((child) => {
|
||||
_collapseNode(child, false, true, false);
|
||||
});
|
||||
});
|
||||
|
@ -731,8 +725,8 @@ window.qBittorrent.PropFiles = (function() {
|
|||
|
||||
const collapseAllNodes = function() {
|
||||
const root = torrentFilesTable.getRoot();
|
||||
root.children.each(function(node) {
|
||||
node.children.each(function(child) {
|
||||
root.children.each((node) => {
|
||||
node.children.each((child) => {
|
||||
_collapseNode(child, true, true, false);
|
||||
});
|
||||
});
|
||||
|
@ -757,7 +751,7 @@ window.qBittorrent.PropFiles = (function() {
|
|||
if (!isChildNode || applyToChildren || !canSkipNode)
|
||||
_updateNodeState(node, shouldCollapse);
|
||||
|
||||
node.children.each(function(child) {
|
||||
node.children.each((child) => {
|
||||
_hideNode(child, shouldCollapse);
|
||||
|
||||
if (!child.isFolder)
|
||||
|
|
|
@ -26,11 +26,10 @@
|
|||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
if (window.qBittorrent === undefined) {
|
||||
if (window.qBittorrent === undefined)
|
||||
window.qBittorrent = {};
|
||||
}
|
||||
|
||||
window.qBittorrent.PropGeneral = (function() {
|
||||
const exports = function() {
|
||||
|
@ -42,42 +41,43 @@ window.qBittorrent.PropGeneral = (function() {
|
|||
const piecesBar = new window.qBittorrent.PiecesBar.PiecesBar([], {
|
||||
height: 16
|
||||
});
|
||||
$('progress').appendChild(piecesBar);
|
||||
$("progress").appendChild(piecesBar);
|
||||
|
||||
const clearData = function() {
|
||||
$('time_elapsed').set('html', '');
|
||||
$('eta').set('html', '');
|
||||
$('nb_connections').set('html', '');
|
||||
$('total_downloaded').set('html', '');
|
||||
$('total_uploaded').set('html', '');
|
||||
$('dl_speed').set('html', '');
|
||||
$('up_speed').set('html', '');
|
||||
$('dl_limit').set('html', '');
|
||||
$('up_limit').set('html', '');
|
||||
$('total_wasted').set('html', '');
|
||||
$('seeds').set('html', '');
|
||||
$('peers').set('html', '');
|
||||
$('share_ratio').set('html', '');
|
||||
$('popularity').set('html', '');
|
||||
$('reannounce').set('html', '');
|
||||
$('last_seen').set('html', '');
|
||||
$('total_size').set('html', '');
|
||||
$('pieces').set('html', '');
|
||||
$('created_by').set('html', '');
|
||||
$('addition_date').set('html', '');
|
||||
$('completion_date').set('html', '');
|
||||
$('creation_date').set('html', '');
|
||||
$('torrent_hash_v1').set('html', '');
|
||||
$('torrent_hash_v2').set('html', '');
|
||||
$('save_path').set('html', '');
|
||||
$('comment').set('html', '');
|
||||
$("time_elapsed").set("html", "");
|
||||
$("eta").set("html", "");
|
||||
$("nb_connections").set("html", "");
|
||||
$("total_downloaded").set("html", "");
|
||||
$("total_uploaded").set("html", "");
|
||||
$("dl_speed").set("html", "");
|
||||
$("up_speed").set("html", "");
|
||||
$("dl_limit").set("html", "");
|
||||
$("up_limit").set("html", "");
|
||||
$("total_wasted").set("html", "");
|
||||
$("seeds").set("html", "");
|
||||
$("peers").set("html", "");
|
||||
$("share_ratio").set("html", "");
|
||||
$("popularity").set("html", "");
|
||||
$("reannounce").set("html", "");
|
||||
$("last_seen").set("html", "");
|
||||
$("total_size").set("html", "");
|
||||
$("pieces").set("html", "");
|
||||
$("created_by").set("html", "");
|
||||
$("addition_date").set("html", "");
|
||||
$("completion_date").set("html", "");
|
||||
$("creation_date").set("html", "");
|
||||
$("torrent_hash_v1").set("html", "");
|
||||
$("torrent_hash_v2").set("html", "");
|
||||
$("save_path").set("html", "");
|
||||
$("comment").set("html", "");
|
||||
$("private").set("html", "");
|
||||
piecesBar.clear();
|
||||
};
|
||||
|
||||
let loadTorrentDataTimer;
|
||||
const loadTorrentData = function() {
|
||||
if ($('prop_general').hasClass('invisible')
|
||||
|| $('propertiesPanel_collapseToggle').hasClass('panel-expand')) {
|
||||
if ($("prop_general").hasClass("invisible")
|
||||
|| $("propertiesPanel_collapseToggle").hasClass("panel-expand")) {
|
||||
// Tab changed, don't do anything
|
||||
return;
|
||||
}
|
||||
|
@ -88,18 +88,18 @@ window.qBittorrent.PropGeneral = (function() {
|
|||
loadTorrentDataTimer = loadTorrentData.delay(5000);
|
||||
return;
|
||||
}
|
||||
const url = new URI('api/v2/torrents/properties?hash=' + current_id);
|
||||
const url = new URI("api/v2/torrents/properties?hash=" + current_id);
|
||||
new Request.JSON({
|
||||
url: url,
|
||||
method: 'get',
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onFailure: function() {
|
||||
$('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]');
|
||||
$("error_div").set("html", "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]");
|
||||
clearTimeout(loadTorrentDataTimer);
|
||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||
},
|
||||
onSuccess: function(data) {
|
||||
$('error_div').set('html', '');
|
||||
$("error_div").set("html", "");
|
||||
if (data) {
|
||||
// Update Torrent data
|
||||
|
||||
|
@ -108,109 +108,118 @@ window.qBittorrent.PropGeneral = (function() {
|
|||
.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').set('html', timeElapsed);
|
||||
$("time_elapsed").set("html", timeElapsed);
|
||||
|
||||
$('eta').set('html', window.qBittorrent.Misc.friendlyDuration(data.eta, window.qBittorrent.Misc.MAX_ETA));
|
||||
$("eta").set("html", 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').set('html', nbConnections);
|
||||
$("nb_connections").set("html", 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').set('html', totalDownloaded);
|
||||
$("total_downloaded").set("html", 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').set('html', totalUploaded);
|
||||
$("total_uploaded").set("html", 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').set('html', dlSpeed);
|
||||
$("dl_speed").set("html", 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').set('html', upSpeed);
|
||||
$("up_speed").set("html", upSpeed);
|
||||
|
||||
const dlLimit = (data.dl_limit === -1)
|
||||
? "∞"
|
||||
: window.qBittorrent.Misc.friendlyUnit(data.dl_limit, true);
|
||||
$('dl_limit').set('html', dlLimit);
|
||||
$("dl_limit").set("html", dlLimit);
|
||||
|
||||
const upLimit = (data.up_limit === -1)
|
||||
? "∞"
|
||||
: window.qBittorrent.Misc.friendlyUnit(data.up_limit, true);
|
||||
$('up_limit').set('html', upLimit);
|
||||
$("up_limit").set("html", upLimit);
|
||||
|
||||
$('total_wasted').set('html', window.qBittorrent.Misc.friendlyUnit(data.total_wasted));
|
||||
$("total_wasted").set("html", 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').set('html', seeds);
|
||||
$("seeds").set("html", seeds);
|
||||
|
||||
const peers = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", data.peers)
|
||||
.replace("%2", data.peers_total);
|
||||
$('peers').set('html', peers);
|
||||
$("peers").set("html", peers);
|
||||
|
||||
$('share_ratio').set('html', data.share_ratio.toFixed(2));
|
||||
$("share_ratio").set("html", data.share_ratio.toFixed(2));
|
||||
|
||||
$('popularity').set('html', data.popularity.toFixed(2));
|
||||
$("popularity").set("html", data.popularity.toFixed(2));
|
||||
|
||||
$('reannounce').set('html', window.qBittorrent.Misc.friendlyDuration(data.reannounce));
|
||||
$("reannounce").set("html", 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').set('html', lastSeen);
|
||||
$("last_seen").set("html", lastSeen);
|
||||
|
||||
const totalSize = (data.total_size >= 0) ? window.qBittorrent.Misc.friendlyUnit(data.total_size) : "";
|
||||
$('total_size').set('html', totalSize);
|
||||
$("total_size").set("html", totalSize);
|
||||
|
||||
const pieces = (data.pieces_num >= 0)
|
||||
? "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
.replace("%1", data.pieces_num)
|
||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.piece_size))
|
||||
.replace("%3", data.pieces_have)
|
||||
: '';
|
||||
$('pieces').set('html', pieces);
|
||||
: "";
|
||||
$("pieces").set("html", pieces);
|
||||
|
||||
$('created_by').set('text', data.created_by);
|
||||
$("created_by").set("text", 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').set('html', additionDate);
|
||||
$("addition_date").set("html", additionDate);
|
||||
|
||||
const completionDate = (data.completion_date >= 0)
|
||||
? new Date(data.completion_date * 1000).toLocaleString()
|
||||
: "";
|
||||
$('completion_date').set('html', completionDate);
|
||||
$("completion_date").set("html", completionDate);
|
||||
|
||||
const creationDate = (data.creation_date >= 0)
|
||||
? new Date(data.creation_date * 1000).toLocaleString()
|
||||
: "";
|
||||
$('creation_date').set('html', creationDate);
|
||||
$("creation_date").set("html", creationDate);
|
||||
|
||||
const torrentHashV1 = (data.infohash_v1 !== "")
|
||||
? data.infohash_v1
|
||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||
$('torrent_hash_v1').set('html', torrentHashV1);
|
||||
$("torrent_hash_v1").set("html", torrentHashV1);
|
||||
|
||||
const torrentHashV2 = (data.infohash_v2 !== "")
|
||||
? data.infohash_v2
|
||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
||||
$('torrent_hash_v2').set('html', torrentHashV2);
|
||||
$("torrent_hash_v2").set("html", torrentHashV2);
|
||||
|
||||
$('save_path').set('html', data.save_path);
|
||||
$("save_path").set("html", data.save_path);
|
||||
|
||||
$('comment').set('html', window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment)));
|
||||
$("comment").set("html", window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment)));
|
||||
|
||||
if (data.has_metadata) {
|
||||
$("private").set("text", (data.private
|
||||
? "QBT_TR(Yes)QBT_TR[CONTEXT=PropertiesWidget]"
|
||||
: "QBT_TR(No)QBT_TR[CONTEXT=PropertiesWidget]"));
|
||||
}
|
||||
else {
|
||||
$("private").set("text", "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
clearData();
|
||||
|
@ -220,25 +229,24 @@ window.qBittorrent.PropGeneral = (function() {
|
|||
}
|
||||
}).send();
|
||||
|
||||
const piecesUrl = new URI('api/v2/torrents/pieceStates?hash=' + current_id);
|
||||
const piecesUrl = new URI("api/v2/torrents/pieceStates?hash=" + current_id);
|
||||
new Request.JSON({
|
||||
url: piecesUrl,
|
||||
method: 'get',
|
||||
method: "get",
|
||||
noCache: true,
|
||||
onFailure: function() {
|
||||
$('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]');
|
||||
$("error_div").set("html", "QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]");
|
||||
clearTimeout(loadTorrentDataTimer);
|
||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||
},
|
||||
onSuccess: function(data) {
|
||||
$('error_div').set('html', '');
|
||||
$("error_div").set("html", "");
|
||||
|
||||
if (data) {
|
||||
if (data)
|
||||
piecesBar.setPieces(data);
|
||||
}
|
||||
else {
|
||||
else
|
||||
clearData();
|
||||
}
|
||||
|
||||
clearTimeout(loadTorrentDataTimer);
|
||||
loadTorrentDataTimer = loadTorrentData.delay(5000);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue