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:
|
env:
|
||||||
boost_path: "${{ github.workspace }}/../boost"
|
boost_path: "${{ github.workspace }}/../boost"
|
||||||
openssl_root: "$(brew --prefix openssl@3)"
|
|
||||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -70,7 +69,7 @@ jobs:
|
||||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: qtbase qtdeclarative qtsvg qttools
|
archives: qtbase qtdeclarative qtsvg qttools
|
||||||
|
@ -94,8 +93,7 @@ jobs:
|
||||||
-DCMAKE_CXX_STANDARD=17 \
|
-DCMAKE_CXX_STANDARD=17 \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||||
-Ddeprecated-functions=OFF \
|
-Ddeprecated-functions=OFF
|
||||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
|
||||||
cmake --build build
|
cmake --build build
|
||||||
sudo cmake --install build
|
sudo cmake --install build
|
||||||
|
|
||||||
|
@ -109,7 +107,6 @@ jobs:
|
||||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
|
||||||
-DTESTING=ON \
|
-DTESTING=ON \
|
||||||
-DVERBOSE_CONFIGURE=ON \
|
-DVERBOSE_CONFIGURE=ON \
|
||||||
-D${{ matrix.qbt_gui }}
|
-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'
|
python-version: '3.7'
|
||||||
|
|
||||||
- name: Install tools (search engine)
|
- name: Install tools (search engine)
|
||||||
run: pip install bandit pycodestyle pyflakes
|
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||||
|
|
||||||
- name: Gather files (search engine)
|
- name: Gather files (search engine)
|
||||||
run: |
|
run: |
|
||||||
|
@ -61,6 +61,16 @@ jobs:
|
||||||
echo $PY_FILES
|
echo $PY_FILES
|
||||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
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)
|
- name: Lint code (search engine)
|
||||||
run: |
|
run: |
|
||||||
pyflakes $PY_FILES
|
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 }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||||
|
@ -138,12 +138,12 @@ jobs:
|
||||||
curl \
|
curl \
|
||||||
-L \
|
-L \
|
||||||
-Z \
|
-Z \
|
||||||
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-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-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
|
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||||
chmod +x \
|
chmod +x \
|
||||||
linuxdeploy-x86_64.AppImage \
|
linuxdeploy-static-x86_64.AppImage \
|
||||||
linuxdeploy-plugin-qt-x86_64.AppImage \
|
linuxdeploy-plugin-qt-static-x86_64.AppImage \
|
||||||
linuxdeploy-plugin-appimage-x86_64.AppImage
|
linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||||
|
|
||||||
- name: Prepare files for AppImage
|
- name: Prepare files for AppImage
|
||||||
|
@ -156,12 +156,12 @@ jobs:
|
||||||
|
|
||||||
- name: Package AppImage
|
- name: Package AppImage
|
||||||
run: |
|
run: |
|
||||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||||
rm qbittorrent/apprun-hooks/*
|
rm qbittorrent/apprun-hooks/*
|
||||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||||
NO_APPSTREAM=1 \
|
NO_APPSTREAM=1 \
|
||||||
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
|
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
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4
|
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 }}"
|
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: "6.7.0"
|
version: "6.7.0"
|
||||||
archives: qtbase qtsvg qttools
|
archives: qtbase qtsvg qttools
|
||||||
|
@ -153,26 +153,26 @@ jobs:
|
||||||
copy build/qbittorrent.pdb upload/qBittorrent
|
copy build/qbittorrent.pdb upload/qBittorrent
|
||||||
copy dist/windows/qt.conf upload/qBittorrent
|
copy dist/windows/qt.conf upload/qBittorrent
|
||||||
# runtimes
|
# runtimes
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||||
mkdir upload/qBittorrent/plugins/iconengines
|
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
|
mkdir upload/qBittorrent/plugins/imageformats
|
||||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
copy "${{ env.Qt_ROOT_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/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||||
mkdir upload/qBittorrent/plugins/platforms
|
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
|
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
|
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
|
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
|
# cmake additionals
|
||||||
mkdir upload/cmake
|
mkdir upload/cmake
|
||||||
copy build/compile_commands.json 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 }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||||
|
|
|
@ -38,6 +38,8 @@ add_library(qbt_base STATIC
|
||||||
bittorrent/torrent.h
|
bittorrent/torrent.h
|
||||||
bittorrent/torrentcontenthandler.h
|
bittorrent/torrentcontenthandler.h
|
||||||
bittorrent/torrentcontentlayout.h
|
bittorrent/torrentcontentlayout.h
|
||||||
|
bittorrent/torrentcontentremoveoption.h
|
||||||
|
bittorrent/torrentcontentremover.h
|
||||||
bittorrent/torrentcreationmanager.h
|
bittorrent/torrentcreationmanager.h
|
||||||
bittorrent/torrentcreationtask.h
|
bittorrent/torrentcreationtask.h
|
||||||
bittorrent/torrentcreator.h
|
bittorrent/torrentcreator.h
|
||||||
|
@ -145,6 +147,7 @@ add_library(qbt_base STATIC
|
||||||
bittorrent/sslparameters.cpp
|
bittorrent/sslparameters.cpp
|
||||||
bittorrent/torrent.cpp
|
bittorrent/torrent.cpp
|
||||||
bittorrent/torrentcontenthandler.cpp
|
bittorrent/torrentcontenthandler.cpp
|
||||||
|
bittorrent/torrentcontentremover.cpp
|
||||||
bittorrent/torrentcreationmanager.cpp
|
bittorrent/torrentcreationmanager.cpp
|
||||||
bittorrent/torrentcreationtask.cpp
|
bittorrent/torrentcreationtask.cpp
|
||||||
bittorrent/torrentcreator.cpp
|
bittorrent/torrentcreator.cpp
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/exceptions.h"
|
#include "base/exceptions.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.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)
|
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)
|
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}
|
, m_savePath {params.path}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,17 +37,12 @@
|
||||||
#include "addtorrentparams.h"
|
#include "addtorrentparams.h"
|
||||||
#include "categoryoptions.h"
|
#include "categoryoptions.h"
|
||||||
#include "sharelimitaction.h"
|
#include "sharelimitaction.h"
|
||||||
|
#include "torrentcontentremoveoption.h"
|
||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
#include "trackerentrystatus.h"
|
#include "trackerentrystatus.h"
|
||||||
|
|
||||||
class QString;
|
class QString;
|
||||||
|
|
||||||
enum DeleteOption
|
|
||||||
{
|
|
||||||
DeleteTorrent,
|
|
||||||
DeleteTorrentAndFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
|
@ -58,6 +53,12 @@ namespace BitTorrent
|
||||||
struct CacheStatus;
|
struct CacheStatus;
|
||||||
struct SessionStatus;
|
struct SessionStatus;
|
||||||
|
|
||||||
|
enum class TorrentRemoveOption
|
||||||
|
{
|
||||||
|
KeepContent,
|
||||||
|
RemoveContent
|
||||||
|
};
|
||||||
|
|
||||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
// 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.
|
// 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
|
// 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 void setExcludedFileNamesEnabled(bool enabled) = 0;
|
||||||
virtual QStringList excludedFileNames() const = 0;
|
virtual QStringList excludedFileNames() const = 0;
|
||||||
virtual void setExcludedFileNames(const QStringList &newList) = 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 QStringList bannedIPs() const = 0;
|
||||||
virtual void setBannedIPs(const QStringList &newList) = 0;
|
virtual void setBannedIPs(const QStringList &newList) = 0;
|
||||||
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
||||||
|
@ -434,6 +435,8 @@ namespace BitTorrent
|
||||||
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
||||||
virtual bool isStartPaused() const = 0;
|
virtual bool isStartPaused() const = 0;
|
||||||
virtual void setStartPaused(bool value) = 0;
|
virtual void setStartPaused(bool value) = 0;
|
||||||
|
virtual TorrentContentRemoveOption torrentContentRemoveOption() const = 0;
|
||||||
|
virtual void setTorrentContentRemoveOption(TorrentContentRemoveOption option) = 0;
|
||||||
|
|
||||||
virtual bool isRestored() const = 0;
|
virtual bool isRestored() const = 0;
|
||||||
|
|
||||||
|
@ -453,7 +456,7 @@ namespace BitTorrent
|
||||||
|
|
||||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 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 downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@
|
||||||
#include "nativesessionextension.h"
|
#include "nativesessionextension.h"
|
||||||
#include "portforwarderimpl.h"
|
#include "portforwarderimpl.h"
|
||||||
#include "resumedatastorage.h"
|
#include "resumedatastorage.h"
|
||||||
|
#include "torrentcontentremover.h"
|
||||||
#include "torrentdescriptor.h"
|
#include "torrentdescriptor.h"
|
||||||
#include "torrentimpl.h"
|
#include "torrentimpl.h"
|
||||||
#include "tracker.h"
|
#include "tracker.h"
|
||||||
|
@ -525,6 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
||||||
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
||||||
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_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_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
||||||
, m_seedingLimitTimer {new QTimer(this)}
|
, m_seedingLimitTimer {new QTimer(this)}
|
||||||
, m_resumeDataTimer {new QTimer(this)}
|
, m_resumeDataTimer {new QTimer(this)}
|
||||||
|
@ -550,7 +552,14 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
, this, [this]() { m_recentErroredTorrents.clear(); });
|
, this, [this]() { m_recentErroredTorrents.clear(); });
|
||||||
|
|
||||||
m_seedingLimitTimer->setInterval(10s);
|
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();
|
initializeNativeSession();
|
||||||
configureComponents();
|
configureComponents();
|
||||||
|
@ -586,6 +595,11 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
|
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
|
||||||
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
|
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();
|
m_ioThread->start();
|
||||||
|
|
||||||
initMetrics();
|
initMetrics();
|
||||||
|
@ -604,7 +618,7 @@ SessionImpl::~SessionImpl()
|
||||||
{
|
{
|
||||||
m_nativeSession->pause();
|
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};
|
const QDeadlineTimer shutdownDeadlineTimer {timeout};
|
||||||
|
|
||||||
if (m_torrentsQueueChanged)
|
if (m_torrentsQueueChanged)
|
||||||
|
@ -2236,72 +2250,66 @@ void SessionImpl::populateAdditionalTrackers()
|
||||||
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
|
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
|
const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
|
||||||
{
|
{
|
||||||
return (limit == useGlobalLimit) ? globalLimit : limit;
|
return (limit == useGlobalLimit) ? globalLimit : limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We shouldn't iterate over `m_torrents` in the loop below
|
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
||||||
// since `deleteTorrent()` modifies it indirectly
|
const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
|
||||||
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
|
||||||
for (const auto &[torrentID, torrent] : torrents.asKeyValueRange())
|
|
||||||
|
bool reached = false;
|
||||||
|
QString description;
|
||||||
|
|
||||||
|
if (const qreal ratio = torrent->realRatio();
|
||||||
|
(ratioLimit >= 0) && (ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
|
||||||
{
|
{
|
||||||
if (!torrent->isFinished() || torrent->isForced())
|
reached = true;
|
||||||
continue;
|
description = tr("Torrent reached the share ratio limit.");
|
||||||
|
}
|
||||||
|
else if (const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
|
||||||
|
(seedingTimeLimit >= 0) && (seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
|
||||||
|
{
|
||||||
|
reached = true;
|
||||||
|
description = tr("Torrent reached the seeding time limit.");
|
||||||
|
}
|
||||||
|
else if (const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
|
||||||
|
(inactiveSeedingTimeLimit >= 0) && (inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
|
||||||
|
{
|
||||||
|
reached = true;
|
||||||
|
description = tr("Torrent reached the inactive seeding time limit.");
|
||||||
|
}
|
||||||
|
|
||||||
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
if (reached)
|
||||||
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());
|
const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
|
||||||
|
const ShareLimitAction shareLimitAction = (torrent->shareLimitAction() == ShareLimitAction::Default) ? m_shareLimitAction : torrent->shareLimitAction();
|
||||||
|
|
||||||
bool reached = false;
|
if (shareLimitAction == ShareLimitAction::Remove)
|
||||||
QString description;
|
|
||||||
|
|
||||||
if (const qreal ratio = torrent->realRatio();
|
|
||||||
(ratioLimit >= 0) && (ratio <= Torrent::MAX_RATIO) && (ratio >= ratioLimit))
|
|
||||||
{
|
{
|
||||||
reached = true;
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
|
||||||
description = tr("Torrent reached the share ratio limit.");
|
removeTorrent(torrent->id(), TorrentRemoveOption::KeepContent);
|
||||||
}
|
}
|
||||||
else if (const qlonglong seedingTimeInMinutes = torrent->finishedTime() / 60;
|
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
|
||||||
(seedingTimeLimit >= 0) && (seedingTimeInMinutes <= Torrent::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit))
|
|
||||||
{
|
{
|
||||||
reached = true;
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
|
||||||
description = tr("Torrent reached the seeding time limit.");
|
removeTorrent(torrent->id(), TorrentRemoveOption::RemoveContent);
|
||||||
}
|
}
|
||||||
else if (const qlonglong inactiveSeedingTimeInMinutes = torrent->timeSinceActivity() / 60;
|
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
|
||||||
(inactiveSeedingTimeLimit >= 0) && (inactiveSeedingTimeInMinutes <= Torrent::MAX_INACTIVE_SEEDING_TIME) && (inactiveSeedingTimeInMinutes >= inactiveSeedingTimeLimit))
|
|
||||||
{
|
{
|
||||||
reached = true;
|
torrent->stop();
|
||||||
description = tr("Torrent reached the inactive seeding time limit.");
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent stopped."), torrentName));
|
||||||
}
|
}
|
||||||
|
else if ((shareLimitAction == ShareLimitAction::EnableSuperSeeding) && !torrent->isStopped() && !torrent->superSeeding())
|
||||||
if (reached)
|
|
||||||
{
|
{
|
||||||
const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
|
torrent->setSuperSeeding(true);
|
||||||
const ShareLimitAction shareLimitAction = (torrent->shareLimitAction() == ShareLimitAction::Default) ? m_shareLimitAction : torrent->shareLimitAction();
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
|
||||||
|
|
||||||
if (shareLimitAction == ShareLimitAction::Remove)
|
|
||||||
{
|
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
|
|
||||||
deleteTorrent(torrentID);
|
|
||||||
}
|
|
||||||
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
|
|
||||||
{
|
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
|
|
||||||
deleteTorrent(torrentID, DeleteTorrentAndFiles);
|
|
||||||
}
|
|
||||||
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
|
|
||||||
{
|
|
||||||
torrent->stop();
|
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Torrent stopped."), torrentName));
|
|
||||||
}
|
|
||||||
else if ((shareLimitAction == ShareLimitAction::EnableSuperSeeding) && !torrent->isStopped() && !torrent->superSeeding())
|
|
||||||
{
|
|
||||||
torrent->setSuperSeeding(true);
|
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Super seeding enabled."), torrentName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
|
||||||
{
|
{
|
||||||
return m_torrents.value(id);
|
return m_torrents.value(id);
|
||||||
|
@ -2377,26 +2398,29 @@ void SessionImpl::banIP(const QString &ip)
|
||||||
|
|
||||||
// Delete a torrent from the session, given its hash
|
// Delete a torrent from the session, given its hash
|
||||||
// and from the disk, if the corresponding deleteOption is chosen
|
// 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);
|
TorrentImpl *const torrent = m_torrents.take(id);
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
return false;
|
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);
|
emit torrentAboutToBeRemoved(torrent);
|
||||||
|
|
||||||
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
||||||
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
||||||
|
|
||||||
// Remove it from session
|
// 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 lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
||||||
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
||||||
, [&nativeHandle](const MoveStorageJob &job)
|
, [&nativeHandle](const MoveStorageJob &job)
|
||||||
{
|
{
|
||||||
return job.torrentHandle == nativeHandle;
|
return job.torrentHandle == nativeHandle;
|
||||||
});
|
});
|
||||||
|
@ -2414,14 +2438,14 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
|
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), torrent->actualFilePaths(), deleteOption};
|
||||||
|
|
||||||
if (m_moveStorageQueue.size() > 1)
|
if (m_moveStorageQueue.size() > 1)
|
||||||
{
|
{
|
||||||
// Delete "move storage job" for the deleted torrent
|
// Delete "move storage job" for the deleted torrent
|
||||||
// (note: we shouldn't delete active job)
|
// (note: we shouldn't delete active job)
|
||||||
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
|
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
|
||||||
, [torrent](const MoveStorageJob &job)
|
, [torrent](const MoveStorageJob &job)
|
||||||
{
|
{
|
||||||
return job.torrentHandle == torrent->nativeHandle();
|
return job.torrentHandle == torrent->nativeHandle();
|
||||||
});
|
});
|
||||||
|
@ -2429,12 +2453,13 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
||||||
m_moveStorageQueue.erase(iter);
|
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
|
// 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;
|
delete torrent;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2462,7 +2487,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
|
m_nativeSession->remove_torrent(nativeHandle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2769,26 +2794,22 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||||
Q_ASSERT(p.file_priorities.empty());
|
Q_ASSERT(p.file_priorities.empty());
|
||||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
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
|
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||||
|
|
||||||
if (addTorrentParams.filePriorities.isEmpty())
|
if (!filePriorities.isEmpty())
|
||||||
{
|
{
|
||||||
if (isExcludedFileNamesEnabled())
|
for (int i = 0; i < filePriorities.size(); ++i)
|
||||||
{
|
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(filePriorities[i]);
|
||||||
// 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]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(p.ti);
|
Q_ASSERT(p.ti);
|
||||||
|
@ -3874,21 +3895,41 @@ void SessionImpl::populateExcludedFileNamesRegExpList()
|
||||||
|
|
||||||
for (const QString &str : excludedNames)
|
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};
|
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
|
||||||
m_excludedFileNamesRegExpList.append(re);
|
m_excludedFileNamesRegExpList.append(re);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SessionImpl::isFilenameExcluded(const QString &fileName) const
|
void SessionImpl::applyFilenameFilter(const PathList &files, QList<DownloadPriority> &priorities)
|
||||||
{
|
{
|
||||||
if (!isExcludedFileNamesEnabled())
|
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)
|
void SessionImpl::setBannedIPs(const QStringList &newList)
|
||||||
|
@ -3957,6 +3998,16 @@ void SessionImpl::setStartPaused(const bool value)
|
||||||
m_startPaused = value;
|
m_startPaused = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TorrentContentRemoveOption SessionImpl::torrentContentRemoveOption() const
|
||||||
|
{
|
||||||
|
return m_torrentContentRemoveOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionImpl::setTorrentContentRemoveOption(const TorrentContentRemoveOption option)
|
||||||
|
{
|
||||||
|
m_torrentContentRemoveOption = option;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList SessionImpl::bannedIPs() const
|
QStringList SessionImpl::bannedIPs() const
|
||||||
{
|
{
|
||||||
return m_bannedIPs;
|
return m_bannedIPs;
|
||||||
|
@ -4890,7 +4941,7 @@ void SessionImpl::updateSeedingLimitTimer()
|
||||||
if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
|
if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
|
||||||
&& (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
|
&& (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
|
||||||
&& (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
|
&& (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
|
||||||
{
|
{
|
||||||
if (m_seedingLimitTimer->isActive())
|
if (m_seedingLimitTimer->isActive())
|
||||||
m_seedingLimitTimer->stop();
|
m_seedingLimitTimer->stop();
|
||||||
}
|
}
|
||||||
|
@ -5002,18 +5053,7 @@ void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
|
||||||
|
|
||||||
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
m_pendingFinishedTorrents.append(torrent);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
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
|
// Last job is completed for torrent that being removing, so actually remove it
|
||||||
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
|
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
|
||||||
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
|
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);
|
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5660,74 +5700,32 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
|
||||||
return torrent;
|
return torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert)
|
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*alert*/)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
// We cannot consider `torrent_removed_alert` as a starting point for removing content,
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
// because it has an inconsistent posting time between different versions of libtorrent,
|
||||||
#else
|
// so files may still be in use in some cases.
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||||
#else
|
#else
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||||
#endif
|
#endif
|
||||||
|
handleRemovedTorrent(torrentID);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||||
#else
|
#else
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||||
#endif
|
#endif
|
||||||
|
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
handleRemovedTorrent(torrentID, errorMessage);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
|
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())
|
if (!updatedTorrents.isEmpty())
|
||||||
emit torrentsUpdated(updatedTorrents);
|
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)
|
if (m_needSaveTorrentsQueue)
|
||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
|
|
||||||
|
@ -6140,7 +6161,7 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
|
||||||
if (torrent2)
|
if (torrent2)
|
||||||
{
|
{
|
||||||
if (torrent1)
|
if (torrent1)
|
||||||
deleteTorrent(torrentIDv1);
|
removeTorrent(torrentIDv1);
|
||||||
else
|
else
|
||||||
cancelDownloadMetadata(torrentIDv1);
|
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 InfoHash;
|
||||||
class ResumeDataStorage;
|
class ResumeDataStorage;
|
||||||
class Torrent;
|
class Torrent;
|
||||||
|
class TorrentContentRemover;
|
||||||
class TorrentDescriptor;
|
class TorrentDescriptor;
|
||||||
class TorrentImpl;
|
class TorrentImpl;
|
||||||
class Tracker;
|
class Tracker;
|
||||||
|
@ -402,7 +403,7 @@ namespace BitTorrent
|
||||||
void setExcludedFileNamesEnabled(bool enabled) override;
|
void setExcludedFileNamesEnabled(bool enabled) override;
|
||||||
QStringList excludedFileNames() const override;
|
QStringList excludedFileNames() const override;
|
||||||
void setExcludedFileNames(const QStringList &excludedFileNames) 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;
|
QStringList bannedIPs() const override;
|
||||||
void setBannedIPs(const QStringList &newList) override;
|
void setBannedIPs(const QStringList &newList) override;
|
||||||
ResumeDataStorageType resumeDataStorageType() const override;
|
ResumeDataStorageType resumeDataStorageType() const override;
|
||||||
|
@ -411,6 +412,8 @@ namespace BitTorrent
|
||||||
void setMergeTrackersEnabled(bool enabled) override;
|
void setMergeTrackersEnabled(bool enabled) override;
|
||||||
bool isStartPaused() const override;
|
bool isStartPaused() const override;
|
||||||
void setStartPaused(bool value) override;
|
void setStartPaused(bool value) override;
|
||||||
|
TorrentContentRemoveOption torrentContentRemoveOption() const override;
|
||||||
|
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
|
||||||
|
|
||||||
bool isRestored() const override;
|
bool isRestored() const override;
|
||||||
|
|
||||||
|
@ -430,7 +433,7 @@ namespace BitTorrent
|
||||||
|
|
||||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) 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 downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||||
|
|
||||||
|
@ -487,11 +490,11 @@ namespace BitTorrent
|
||||||
void configureDeferred();
|
void configureDeferred();
|
||||||
void readAlerts();
|
void readAlerts();
|
||||||
void enqueueRefresh();
|
void enqueueRefresh();
|
||||||
void processShareLimits();
|
|
||||||
void generateResumeData();
|
void generateResumeData();
|
||||||
void handleIPFilterParsed(int ruleCount);
|
void handleIPFilterParsed(int ruleCount);
|
||||||
void handleIPFilterError();
|
void handleIPFilterError();
|
||||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||||
|
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ResumeSessionContext;
|
struct ResumeSessionContext;
|
||||||
|
@ -507,8 +510,9 @@ namespace BitTorrent
|
||||||
struct RemovingTorrentData
|
struct RemovingTorrentData
|
||||||
{
|
{
|
||||||
QString name;
|
QString name;
|
||||||
Path pathToRemove;
|
Path contentStoragePath;
|
||||||
DeleteOption deleteOption {};
|
PathList fileNames;
|
||||||
|
TorrentRemoveOption removeOption {};
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit SessionImpl(QObject *parent = nullptr);
|
explicit SessionImpl(QObject *parent = nullptr);
|
||||||
|
@ -536,6 +540,7 @@ namespace BitTorrent
|
||||||
void enableIPFilter();
|
void enableIPFilter();
|
||||||
void disableIPFilter();
|
void disableIPFilter();
|
||||||
void processTrackerStatuses();
|
void processTrackerStatuses();
|
||||||
|
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||||
void populateExcludedFileNamesRegExpList();
|
void populateExcludedFileNamesRegExpList();
|
||||||
void prepareStartup();
|
void prepareStartup();
|
||||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
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);
|
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
||||||
|
|
||||||
// BitTorrent
|
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
|
||||||
lt::session *m_nativeSession = nullptr;
|
|
||||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
|
||||||
|
|
||||||
bool m_deferredConfigureScheduled = false;
|
|
||||||
bool m_IPFilteringConfigured = false;
|
|
||||||
mutable bool m_listenInterfaceConfigured = false;
|
|
||||||
|
|
||||||
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
||||||
CachedSettingValue<bool> m_isDHTEnabled;
|
CachedSettingValue<bool> m_isDHTEnabled;
|
||||||
|
@ -731,8 +730,16 @@ namespace BitTorrent
|
||||||
CachedSettingValue<int> m_I2POutboundQuantity;
|
CachedSettingValue<int> m_I2POutboundQuantity;
|
||||||
CachedSettingValue<int> m_I2PInboundLength;
|
CachedSettingValue<int> m_I2PInboundLength;
|
||||||
CachedSettingValue<int> m_I2POutboundLength;
|
CachedSettingValue<int> m_I2POutboundLength;
|
||||||
|
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
|
||||||
SettingValue<bool> m_startPaused;
|
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_isRestored = false;
|
||||||
bool m_isPaused = isStartPaused();
|
bool m_isPaused = isStartPaused();
|
||||||
|
|
||||||
|
@ -766,6 +773,7 @@ namespace BitTorrent
|
||||||
QThreadPool *m_asyncWorker = nullptr;
|
QThreadPool *m_asyncWorker = nullptr;
|
||||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||||
FileSearcher *m_fileSearcher = nullptr;
|
FileSearcher *m_fileSearcher = nullptr;
|
||||||
|
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||||
|
|
||||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||||
|
|
||||||
|
@ -809,6 +817,8 @@ namespace BitTorrent
|
||||||
QTimer *m_wakeupCheckTimer = nullptr;
|
QTimer *m_wakeupCheckTimer = nullptr;
|
||||||
QDateTime m_wakeupCheckTimestamp;
|
QDateTime m_wakeupCheckTimestamp;
|
||||||
|
|
||||||
|
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||||
|
|
||||||
friend void Session::initInstance();
|
friend void Session::initInstance();
|
||||||
friend void Session::freeInstance();
|
friend void Session::freeInstance();
|
||||||
friend Session *Session::instance();
|
friend Session *Session::instance();
|
||||||
|
|
|
@ -228,6 +228,7 @@ namespace BitTorrent
|
||||||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||||
|
|
||||||
virtual PathList filePaths() const = 0;
|
virtual PathList filePaths() const = 0;
|
||||||
|
virtual PathList actualFilePaths() const = 0;
|
||||||
|
|
||||||
virtual TorrentInfo info() const = 0;
|
virtual TorrentInfo info() const = 0;
|
||||||
virtual bool isFinished() 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"
|
#include "base/utils/os.h"
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
|
||||||
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
#include "customstorage.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace BitTorrent;
|
using namespace BitTorrent;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -982,6 +986,21 @@ PathList TorrentImpl::filePaths() const
|
||||||
return m_filePaths;
|
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
|
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
||||||
{
|
{
|
||||||
return m_filePriorities;
|
return m_filePriorities;
|
||||||
|
@ -1447,11 +1466,13 @@ QBitArray TorrentImpl::pieces() const
|
||||||
|
|
||||||
QBitArray TorrentImpl::downloadingPieces() const
|
QBitArray TorrentImpl::downloadingPieces() const
|
||||||
{
|
{
|
||||||
QBitArray result(piecesCount());
|
if (!hasMetadata())
|
||||||
|
return {};
|
||||||
|
|
||||||
std::vector<lt::partial_piece_info> queue;
|
std::vector<lt::partial_piece_info> queue;
|
||||||
m_nativeHandle.get_download_queue(queue);
|
m_nativeHandle.get_download_queue(queue);
|
||||||
|
|
||||||
|
QBitArray result {piecesCount()};
|
||||||
for (const lt::partial_piece_info &info : queue)
|
for (const lt::partial_piece_info &info : queue)
|
||||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
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);
|
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
||||||
m_filePaths.append(filePath);
|
m_filePaths.append(filePath);
|
||||||
|
|
||||||
lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
|
m_filePriorities.append(LT::fromNative(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_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.save_path = savePath.toString().toStdString();
|
||||||
p.ti = metadata;
|
p.ti = metadata;
|
||||||
|
|
||||||
|
@ -1859,6 +1881,9 @@ void TorrentImpl::reload()
|
||||||
|
|
||||||
auto *const extensionData = new ExtensionData;
|
auto *const extensionData = new ExtensionData;
|
||||||
p.userdata = LTClientData(extensionData);
|
p.userdata = LTClientData(extensionData);
|
||||||
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
p.storage = customStorageConstructor;
|
||||||
|
#endif
|
||||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||||
|
|
||||||
m_nativeStatus = extensionData->status;
|
m_nativeStatus = extensionData->status;
|
||||||
|
|
|
@ -153,6 +153,7 @@ namespace BitTorrent
|
||||||
Path actualFilePath(int index) const override;
|
Path actualFilePath(int index) const override;
|
||||||
qlonglong fileSize(int index) const override;
|
qlonglong fileSize(int index) const override;
|
||||||
PathList filePaths() const override;
|
PathList filePaths() const override;
|
||||||
|
PathList actualFilePaths() const override;
|
||||||
QVector<DownloadPriority> filePriorities() const override;
|
QVector<DownloadPriority> filePriorities() const override;
|
||||||
|
|
||||||
TorrentInfo info() const override;
|
TorrentInfo info() const override;
|
||||||
|
|
|
@ -44,6 +44,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||||
, m_requestHandler(requestHandler)
|
, m_requestHandler(requestHandler)
|
||||||
{
|
{
|
||||||
m_socket->setParent(this);
|
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
|
// reserve common size for requests, don't use the max allowed size which is too big for
|
||||||
// memory constrained platforms
|
// memory constrained platforms
|
||||||
|
@ -62,11 +63,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection::~Connection()
|
|
||||||
{
|
|
||||||
m_socket->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::read()
|
void Connection::read()
|
||||||
{
|
{
|
||||||
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
||||||
|
@ -182,11 +178,6 @@ bool Connection::hasExpired(const qint64 timeout) const
|
||||||
&& m_idleTimer.hasExpired(timeout);
|
&& m_idleTimer.hasExpired(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Connection::isClosed() const
|
|
||||||
{
|
|
||||||
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Connection::acceptsGzipEncoding(QString codings)
|
bool Connection::acceptsGzipEncoding(QString codings)
|
||||||
{
|
{
|
||||||
// [rfc7231] 5.3.4. Accept-Encoding
|
// [rfc7231] 5.3.4. Accept-Encoding
|
||||||
|
|
|
@ -47,10 +47,11 @@ namespace Http
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||||
~Connection();
|
|
||||||
|
|
||||||
bool hasExpired(qint64 timeout) const;
|
bool hasExpired(qint64 timeout) const;
|
||||||
bool isClosed() const;
|
|
||||||
|
signals:
|
||||||
|
void closed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool acceptsGzipEncoding(QString codings);
|
static bool acceptsGzipEncoding(QString codings);
|
||||||
|
|
|
@ -32,7 +32,10 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#include <QtLogging>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QSslCipher>
|
#include <QSslCipher>
|
||||||
#include <QSslConfiguration>
|
#include <QSslConfiguration>
|
||||||
|
@ -40,7 +43,6 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
#include "base/utils/sslkey.h"
|
#include "base/utils/sslkey.h"
|
||||||
|
@ -113,32 +115,38 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||||
|
|
||||||
void Server::incomingConnection(const qintptr socketDescriptor)
|
void Server::incomingConnection(const qintptr socketDescriptor)
|
||||||
{
|
{
|
||||||
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
std::unique_ptr<QTcpSocket> serverSocket = m_https ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
|
||||||
|
|
||||||
QTcpSocket *serverSocket = nullptr;
|
|
||||||
if (m_https)
|
|
||||||
serverSocket = new QSslSocket(this);
|
|
||||||
else
|
|
||||||
serverSocket = new QTcpSocket(this);
|
|
||||||
|
|
||||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_https)
|
try
|
||||||
{
|
{
|
||||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
if (m_https)
|
||||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
{
|
||||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
|
||||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
sslSocket->setProtocol(QSsl::SecureProtocols);
|
||||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
sslSocket->setPrivateKey(m_key);
|
||||||
}
|
sslSocket->setLocalCertificateChain(m_certificates);
|
||||||
|
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
|
sslSocket->startServerEncryption();
|
||||||
|
}
|
||||||
|
|
||||||
auto *c = new Connection(serverSocket, m_requestHandler, this);
|
auto *connection = new Connection(serverSocket.release(), m_requestHandler, this);
|
||||||
m_connections.insert(c);
|
m_connections.insert(connection);
|
||||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
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)
|
void Server::removeConnection(Connection *connection)
|
||||||
|
|
|
@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
|
||||||
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Preferences::deleteTorrentFilesAsDefault() const
|
bool Preferences::removeTorrentContent() const
|
||||||
{
|
{
|
||||||
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
|
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;
|
return;
|
||||||
|
|
||||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
|
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Preferences::confirmOnExit() const
|
bool Preferences::confirmOnExit() const
|
||||||
|
|
|
@ -105,8 +105,8 @@ public:
|
||||||
void setUseCustomUITheme(bool use);
|
void setUseCustomUITheme(bool use);
|
||||||
Path customUIThemePath() const;
|
Path customUIThemePath() const;
|
||||||
void setCustomUIThemePath(const Path &path);
|
void setCustomUIThemePath(const Path &path);
|
||||||
bool deleteTorrentFilesAsDefault() const;
|
bool removeTorrentContent() const;
|
||||||
void setDeleteTorrentFilesAsDefault(bool del);
|
void setRemoveTorrentContent(bool remove);
|
||||||
bool confirmOnExit() const;
|
bool confirmOnExit() const;
|
||||||
void setConfirmOnExit(bool confirm);
|
void setConfirmOnExit(bool confirm);
|
||||||
bool speedInTitleBar() const;
|
bool speedInTitleBar() const;
|
||||||
|
|
|
@ -52,19 +52,21 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||||
using BitTorrent::Torrent;
|
using BitTorrent::Torrent;
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
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_type {type}
|
||||||
, m_category {category}
|
, m_category {category}
|
||||||
, m_tag {tag}
|
, m_tag {tag}
|
||||||
, m_idSet {idSet}
|
, m_idSet {idSet}
|
||||||
|
, m_private {isPrivate}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
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_category {category}
|
||||||
, m_tag {tag}
|
, m_tag {tag}
|
||||||
, m_idSet {idSet}
|
, m_idSet {idSet}
|
||||||
|
, m_private {isPrivate}
|
||||||
{
|
{
|
||||||
setTypeByName(filter);
|
setTypeByName(filter);
|
||||||
}
|
}
|
||||||
|
@ -147,11 +149,22 @@ bool TorrentFilter::setTag(const std::optional<Tag> &tag)
|
||||||
return false;
|
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
|
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||||
{
|
{
|
||||||
if (!torrent) return false;
|
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
|
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);
|
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;
|
TorrentFilter() = default;
|
||||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||||
TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID
|
TorrentFilter(Type type
|
||||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tag = AnyTag);
|
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||||
TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID
|
, const std::optional<QString> &category = AnyCategory
|
||||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tags = AnyTag);
|
, 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 setType(Type type);
|
||||||
bool setTypeByName(const QString &filter);
|
bool setTypeByName(const QString &filter);
|
||||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||||
bool setCategory(const std::optional<QString> &category);
|
bool setCategory(const std::optional<QString> &category);
|
||||||
bool setTag(const std::optional<Tag> &tag);
|
bool setTag(const std::optional<Tag> &tag);
|
||||||
|
bool setPrivate(std::optional<bool> isPrivate);
|
||||||
|
|
||||||
bool match(const BitTorrent::Torrent *torrent) const;
|
bool match(const BitTorrent::Torrent *torrent) const;
|
||||||
|
|
||||||
|
@ -105,9 +113,11 @@ private:
|
||||||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||||
|
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||||
|
|
||||||
Type m_type {All};
|
Type m_type {All};
|
||||||
std::optional<QString> m_category;
|
std::optional<QString> m_category;
|
||||||
std::optional<Tag> m_tag;
|
std::optional<Tag> m_tag;
|
||||||
std::optional<TorrentIDSet> m_idSet;
|
std::optional<TorrentIDSet> m_idSet;
|
||||||
|
std::optional<bool> m_private;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -29,8 +29,6 @@
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstring>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
|
@ -52,6 +50,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#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.
|
* 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()};
|
QFile file {path.data()};
|
||||||
|
if (file.remove())
|
||||||
|
return {};
|
||||||
|
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
return true;
|
return {};
|
||||||
|
|
||||||
// Make sure we have read/write permissions
|
// Make sure we have read/write permissions
|
||||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
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)
|
bool Utils::Fs::isReadable(const Path &path)
|
||||||
{
|
{
|
||||||
return QFileInfo(path.data()).isReadable();
|
return QFileInfo(path.data()).isReadable();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include "base/3rdparty/expected.hpp"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ namespace Utils::Fs
|
||||||
|
|
||||||
bool copyFile(const Path &from, const Path &to);
|
bool copyFile(const Path &from, const Path &to);
|
||||||
bool renameFile(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 mkdir(const Path &dirPath);
|
||||||
bool mkpath(const Path &dirPath);
|
bool mkpath(const Path &dirPath);
|
||||||
bool rmdir(const Path &dirPath);
|
bool rmdir(const Path &dirPath);
|
||||||
|
|
|
@ -52,6 +52,8 @@ add_library(qbt_gui STATIC
|
||||||
desktopintegration.h
|
desktopintegration.h
|
||||||
downloadfromurldialog.h
|
downloadfromurldialog.h
|
||||||
executionlogwidget.h
|
executionlogwidget.h
|
||||||
|
filterpatternformat.h
|
||||||
|
filterpatternformatmenu.h
|
||||||
flowlayout.h
|
flowlayout.h
|
||||||
fspathedit.h
|
fspathedit.h
|
||||||
fspathedit_p.h
|
fspathedit_p.h
|
||||||
|
@ -151,6 +153,7 @@ add_library(qbt_gui STATIC
|
||||||
desktopintegration.cpp
|
desktopintegration.cpp
|
||||||
downloadfromurldialog.cpp
|
downloadfromurldialog.cpp
|
||||||
executionlogwidget.cpp
|
executionlogwidget.cpp
|
||||||
|
filterpatternformatmenu.cpp
|
||||||
flowlayout.cpp
|
flowlayout.cpp
|
||||||
fspathedit.cpp
|
fspathedit.cpp
|
||||||
fspathedit_p.cpp
|
fspathedit_p.cpp
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -64,6 +64,7 @@
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
#include "filterpatternformatmenu.h"
|
||||||
#include "lineedit.h"
|
#include "lineedit.h"
|
||||||
#include "torrenttagsdialog.h"
|
#include "torrenttagsdialog.h"
|
||||||
|
|
||||||
|
@ -181,6 +182,11 @@ public:
|
||||||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
|
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
|
void renameFile(const int index, const Path &newFilePath) override
|
||||||
{
|
{
|
||||||
Q_ASSERT((index >= 0) && (index < filesCount()));
|
Q_ASSERT((index >= 0) && (index < filesCount()));
|
||||||
|
@ -290,6 +296,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
||||||
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
||||||
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
||||||
|
, m_storeFilterPatternFormat {u"GUI/" SETTINGS_KEY(u"FilterPatternFormat"_s)}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -316,6 +323,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
// Torrent content filtering
|
// Torrent content filtering
|
||||||
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
||||||
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
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);
|
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
|
||||||
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
||||||
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
||||||
|
@ -360,7 +369,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
});
|
});
|
||||||
dlg->open();
|
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->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||||
connect(Preferences::instance(), &Preferences::changed, []
|
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()
|
void AddNewTorrentDialog::populateSavePaths()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentContext);
|
Q_ASSERT(m_currentContext);
|
||||||
|
@ -886,15 +917,7 @@ void AddNewTorrentDialog::setupTreeview()
|
||||||
{
|
{
|
||||||
// Check file name blacklist for torrents that are manually added
|
// Check file name blacklist for torrents that are manually added
|
||||||
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
||||||
for (int i = 0; i < priorities.size(); ++i)
|
BitTorrent::Session::instance()->applyFilenameFilter(m_contentAdaptor->filePaths(), priorities);
|
||||||
{
|
|
||||||
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
|
|
||||||
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_contentAdaptor->prioritizeFiles(priorities);
|
m_contentAdaptor->prioritizeFiles(priorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
|
#include "filterpatternformat.h"
|
||||||
|
|
||||||
class LineEdit;
|
class LineEdit;
|
||||||
|
|
||||||
|
@ -92,6 +93,8 @@ private:
|
||||||
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
||||||
void setupTreeview();
|
void setupTreeview();
|
||||||
void saveTorrentFile();
|
void saveTorrentFile();
|
||||||
|
void showContentFilterContextMenu();
|
||||||
|
void setContentFilterPattern();
|
||||||
|
|
||||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||||
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
||||||
|
@ -107,4 +110,5 @@ private:
|
||||||
SettingValue<bool> m_storeRememberLastSavePath;
|
SettingValue<bool> m_storeRememberLastSavePath;
|
||||||
SettingValue<QByteArray> m_storeTreeHeaderState;
|
SettingValue<QByteArray> m_storeTreeHeaderState;
|
||||||
SettingValue<QByteArray> m_storeSplitterState;
|
SettingValue<QByteArray> m_storeSplitterState;
|
||||||
|
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||||
};
|
};
|
||||||
|
|
|
@ -63,6 +63,7 @@ namespace
|
||||||
// qBittorrent section
|
// qBittorrent section
|
||||||
QBITTORRENT_HEADER,
|
QBITTORRENT_HEADER,
|
||||||
RESUME_DATA_STORAGE,
|
RESUME_DATA_STORAGE,
|
||||||
|
TORRENT_CONTENT_REMOVE_OPTION,
|
||||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||||
MEMORY_WORKING_SET_LIMIT,
|
MEMORY_WORKING_SET_LIMIT,
|
||||||
#endif
|
#endif
|
||||||
|
@ -364,6 +365,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
||||||
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
session->setTorrentContentRemoveOption(m_comboBoxTorrentContentRemoveOption.currentData().value<BitTorrent::TorrentContentRemoveOption>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef QBT_USES_LIBTORRENT2
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
@ -472,6 +475,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
||||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
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)
|
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||||
// Physical memory (RAM) usage limit
|
// Physical memory (RAM) usage limit
|
||||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||||
|
|
|
@ -81,7 +81,7 @@ private:
|
||||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
||||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
||||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
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;
|
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
||||||
|
|
||||||
#ifndef QBT_USES_LIBTORRENT2
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "uithememanager.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->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
|
||||||
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
||||||
|
|
||||||
m_ui->checkPermDelete->setChecked(defaultDeleteFiles || Preferences::instance()->deleteTorrentFilesAsDefault());
|
m_ui->checkRemoveContent->setChecked(defaultDeleteFiles || Preferences::instance()->removeTorrentContent());
|
||||||
connect(m_ui->checkPermDelete, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
connect(m_ui->checkRemoveContent, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
||||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
||||||
|
|
||||||
|
@ -67,18 +69,18 @@ DeletionConfirmationDialog::~DeletionConfirmationDialog()
|
||||||
delete m_ui;
|
delete m_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeletionConfirmationDialog::isDeleteFileSelected() const
|
bool DeletionConfirmationDialog::isRemoveContentSelected() const
|
||||||
{
|
{
|
||||||
return m_ui->checkPermDelete->isChecked();
|
return m_ui->checkRemoveContent->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeletionConfirmationDialog::updateRememberButtonState()
|
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()
|
void DeletionConfirmationDialog::on_rememberBtn_clicked()
|
||||||
{
|
{
|
||||||
Preferences::instance()->setDeleteTorrentFilesAsDefault(m_ui->checkPermDelete->isChecked());
|
Preferences::instance()->setRemoveTorrentContent(m_ui->checkRemoveContent->isChecked());
|
||||||
m_ui->rememberBtn->setEnabled(false);
|
m_ui->rememberBtn->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -37,16 +38,16 @@ namespace Ui
|
||||||
class DeletionConfirmationDialog;
|
class DeletionConfirmationDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeletionConfirmationDialog : public QDialog
|
class DeletionConfirmationDialog final : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
||||||
~DeletionConfirmationDialog();
|
~DeletionConfirmationDialog() override;
|
||||||
|
|
||||||
bool isDeleteFileSelected() const;
|
bool isRemoveContentSelected() const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateRememberButtonState();
|
void updateRememberButtonState();
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkPermDelete">
|
<widget class="QCheckBox" name="checkRemoveContent">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Also permanently delete the files</string>
|
<string>Also remove the content files</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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;
|
return;
|
||||||
|
|
||||||
// Remove I2P peers since they will be completely reloaded.
|
// 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_listModel->removeRow(item->row());
|
||||||
m_I2PPeerItems.clear();
|
m_I2PPeerItems.clear();
|
||||||
|
|
||||||
|
@ -466,10 +466,14 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||||
{
|
{
|
||||||
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
||||||
|
|
||||||
QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
|
const auto items = m_itemsByIP.find(peerEndpoint.address.ip);
|
||||||
items.remove(item);
|
Q_ASSERT(items != m_itemsByIP.end());
|
||||||
if (items.isEmpty())
|
if (items == m_itemsByIP.end()) [[unlikely]]
|
||||||
m_itemsByIP.remove(peerEndpoint.address.ip);
|
continue;
|
||||||
|
|
||||||
|
items->remove(item);
|
||||||
|
if (items->isEmpty())
|
||||||
|
m_itemsByIP.erase(items);
|
||||||
|
|
||||||
m_listModel->removeRow(item->row());
|
m_listModel->removeRow(item->row());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "gui/autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
|
#include "gui/filterpatternformatmenu.h"
|
||||||
#include "gui/lineedit.h"
|
#include "gui/lineedit.h"
|
||||||
#include "gui/trackerlist/trackerlistwidget.h"
|
#include "gui/trackerlist/trackerlistwidget.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_ui {new Ui::PropertiesWidget}
|
, m_ui {new Ui::PropertiesWidget}
|
||||||
|
, m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
|
@ -78,7 +80,9 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||||
m_contentFilterLine = new LineEdit(this);
|
m_contentFilterLine = new LineEdit(this);
|
||||||
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
||||||
m_contentFilterLine->setFixedWidth(300);
|
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->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
||||||
|
|
||||||
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
||||||
|
@ -206,6 +210,7 @@ void PropertiesWidget::clear()
|
||||||
m_ui->labelSavePathVal->clear();
|
m_ui->labelSavePathVal->clear();
|
||||||
m_ui->labelCreatedOnVal->clear();
|
m_ui->labelCreatedOnVal->clear();
|
||||||
m_ui->labelTotalPiecesVal->clear();
|
m_ui->labelTotalPiecesVal->clear();
|
||||||
|
m_ui->labelPrivateVal->clear();
|
||||||
m_ui->labelInfohash1Val->clear();
|
m_ui->labelInfohash1Val->clear();
|
||||||
m_ui->labelInfohash2Val->clear();
|
m_ui->labelInfohash2Val->clear();
|
||||||
m_ui->labelCommentVal->clear();
|
m_ui->labelCommentVal->clear();
|
||||||
|
@ -274,6 +279,28 @@ void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
|
||||||
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
|
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)
|
void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||||
{
|
{
|
||||||
if (torrent == m_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->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
|
||||||
|
|
||||||
m_ui->labelCreatedByVal->setText(m_torrent->creator());
|
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
|
// Load dynamic data
|
||||||
loadDynamicData();
|
loadDynamicData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "base/pathfwd.h"
|
#include "base/settingvalue.h"
|
||||||
|
#include "gui/filterpatternformat.h"
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
|
@ -102,6 +103,8 @@ private slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *getButtonFromIndex(int index);
|
QPushButton *getButtonFromIndex(int index);
|
||||||
|
void showContentFilterContextMenu();
|
||||||
|
void setContentFilterPattern();
|
||||||
|
|
||||||
Ui::PropertiesWidget *m_ui = nullptr;
|
Ui::PropertiesWidget *m_ui = nullptr;
|
||||||
BitTorrent::Torrent *m_torrent = nullptr;
|
BitTorrent::Torrent *m_torrent = nullptr;
|
||||||
|
@ -115,4 +118,6 @@ private:
|
||||||
PropTabBar *m_tabBar = nullptr;
|
PropTabBar *m_tabBar = nullptr;
|
||||||
LineEdit *m_contentFilterLine = nullptr;
|
LineEdit *m_contentFilterLine = nullptr;
|
||||||
int m_handleWidth = -1;
|
int m_handleWidth = -1;
|
||||||
|
|
||||||
|
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||||
};
|
};
|
||||||
|
|
|
@ -823,6 +823,38 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<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">
|
<widget class="QLabel" name="labelInfohash1">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
@ -838,71 +870,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<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">
|
<widget class="QLabel" name="labelInfohash1Val">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
@ -918,7 +886,55 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<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">
|
<widget class="QLabel" name="labelSavePathVal">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
@ -937,7 +953,23 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<widget class="QLabel" name="labelCommentVal">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -37,17 +37,12 @@
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS)
|
||||||
#include <windows.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
#else
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QMimeType>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN || defined Q_OS_MACOS
|
|
||||||
#define QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
#define QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
|
#elif !defined(Q_OS_WIN)
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QMimeType>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
|
@ -116,27 +111,8 @@ namespace
|
||||||
};
|
};
|
||||||
#endif // QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
#endif // QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS)
|
||||||
// See QTBUG-25319 for explanation why this is required
|
// There is a bug on macOS, to be reported to Qt
|
||||||
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
|
|
||||||
// https://github.com/qbittorrent/qBittorrent/pull/6156#issuecomment-316302615
|
// https://github.com/qbittorrent/qBittorrent/pull/6156#issuecomment-316302615
|
||||||
class MacFileIconProvider final : public CachingFileIconProvider
|
class MacFileIconProvider final : public CachingFileIconProvider
|
||||||
{
|
{
|
||||||
|
@ -145,7 +121,7 @@ namespace
|
||||||
return MacUtils::pixmapForExtension(ext, QSize(32, 32));
|
return MacUtils::pixmapForExtension(ext, QSize(32, 32));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#else
|
#elif !defined(Q_OS_WIN)
|
||||||
/**
|
/**
|
||||||
* @brief Tests whether QFileIconProvider actually works
|
* @brief Tests whether QFileIconProvider actually works
|
||||||
*
|
*
|
||||||
|
@ -189,7 +165,7 @@ TorrentContentModel::TorrentContentModel(QObject *parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
, m_rootItem(new TorrentContentModelFolder(QVector<QString>({ tr("Name"), tr("Total Size"), tr("Progress"), tr("Download Priority"), tr("Remaining"), tr("Availability") })))
|
, 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)
|
#if defined(Q_OS_WIN)
|
||||||
, m_fileIconProvider {new WinShellFileIconProvider}
|
, m_fileIconProvider {new QFileIconProvider}
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_MACOS)
|
||||||
, m_fileIconProvider {new MacFileIconProvider}
|
, m_fileIconProvider {new MacFileIconProvider}
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -147,10 +147,19 @@ void TorrentContentModelFolder::recalculateProgress()
|
||||||
tRemaining += child->remaining();
|
tRemaining += child->remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isRootItem() && (tSize > 0))
|
if (!isRootItem())
|
||||||
{
|
{
|
||||||
m_progress = tProgress / tSize;
|
if (tSize > 0)
|
||||||
m_remaining = tRemaining;
|
{
|
||||||
|
m_progress = tProgress / tSize;
|
||||||
|
m_remaining = tRemaining;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_progress = 1.0;
|
||||||
|
m_remaining = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Q_ASSERT(m_progress <= 1.);
|
Q_ASSERT(m_progress <= 1.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -56,6 +56,19 @@
|
||||||
#include "gui/macutilities.h"
|
#include "gui/macutilities.h"
|
||||||
#endif
|
#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)
|
TorrentContentWidget::TorrentContentWidget(QWidget *parent)
|
||||||
: QTreeView(parent)
|
: QTreeView(parent)
|
||||||
{
|
{
|
||||||
|
@ -173,10 +186,20 @@ Path TorrentContentWidget::getItemPath(const QModelIndex &index) const
|
||||||
return path;
|
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->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
{
|
||||||
|
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())
|
if (patternText.isEmpty())
|
||||||
{
|
{
|
||||||
collapseAll();
|
collapseAll();
|
||||||
|
@ -219,9 +242,9 @@ void TorrentContentWidget::keyPressEvent(QKeyEvent *event)
|
||||||
|
|
||||||
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
|
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
|
||||||
? Qt::Unchecked : 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);
|
model()->setData(index, state, Qt::CheckStateRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,10 +271,10 @@ void TorrentContentWidget::renameSelectedFile()
|
||||||
|
|
||||||
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
|
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
|
||||||
{
|
{
|
||||||
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
|
const QList<QPersistentModelIndex> selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority));
|
||||||
for (const QModelIndex &index : selectedRows)
|
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
|
// a download priority that will apply to each item. The number of groups depends on how
|
||||||
// many "download priority" are available to be assigned
|
// 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 qsizetype priorityGroups = 3;
|
||||||
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
|
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
|
||||||
|
@ -283,8 +306,8 @@ void TorrentContentWidget::applyPrioritiesByOrder()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QModelIndex &index = selectedRows[i];
|
const QPersistentModelIndex &index = selectedRows[i];
|
||||||
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
|
model()->setData(index, static_cast<int>(priority));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
|
#include "filterpatternformat.h"
|
||||||
|
|
||||||
class QShortcut;
|
class QShortcut;
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ public:
|
||||||
int getFileIndex(const QModelIndex &index) const;
|
int getFileIndex(const QModelIndex &index) const;
|
||||||
Path getItemPath(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 checkAll();
|
||||||
void checkNone();
|
void checkNone();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "autoexpandabledialog.h"
|
#include "autoexpandabledialog.h"
|
||||||
#include "flowlayout.h"
|
#include "flowlayout.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#include "ui_torrenttagsdialog.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::accepted, this, &QDialog::accept);
|
||||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
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())))
|
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))
|
if (initialTags.contains(tag))
|
||||||
tagWidget->setChecked(true);
|
tagWidget->setChecked(true);
|
||||||
tagsLayout->addWidget(tagWidget);
|
tagsLayout->addWidget(tagWidget);
|
||||||
|
@ -78,12 +79,12 @@ TorrentTagsDialog::~TorrentTagsDialog()
|
||||||
TagSet TorrentTagsDialog::tags() const
|
TagSet TorrentTagsDialog::tags() const
|
||||||
{
|
{
|
||||||
TagSet tags;
|
TagSet tags;
|
||||||
auto *layout = m_ui->scrollArea->layout();
|
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||||
for (int i = 0; i < (layout->count() - 1); ++i)
|
for (int i = 0; i < (layout->count() - 1); ++i)
|
||||||
{
|
{
|
||||||
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
|
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
|
||||||
if (tagWidget->isChecked())
|
if (tagWidget->isChecked())
|
||||||
tags.insert(Tag(tagWidget->text()));
|
tags.insert(Utils::Gui::widgetTextToTag(tagWidget->text()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
|
@ -111,9 +112,9 @@ void TorrentTagsDialog::addNewTag()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto *layout = m_ui->scrollArea->layout();
|
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||||
auto *btn = layout->takeAt(layout->count() - 1);
|
auto *btn = layout->takeAt(layout->count() - 1);
|
||||||
auto *tagWidget = new QCheckBox(tag.toString());
|
auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag));
|
||||||
tagWidget->setChecked(true);
|
tagWidget->setChecked(true);
|
||||||
layout->addWidget(tagWidget);
|
layout->addWidget(tagWidget);
|
||||||
layout->addItem(btn);
|
layout->addItem(btn);
|
||||||
|
|
|
@ -488,11 +488,11 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
||||||
switch (section)
|
switch (section)
|
||||||
{
|
{
|
||||||
case COL_URL:
|
case COL_URL:
|
||||||
return tr("URL/Announce endpoint");
|
return tr("URL/Announce Endpoint");
|
||||||
case COL_TIER:
|
case COL_TIER:
|
||||||
return tr("Tier");
|
return tr("Tier");
|
||||||
case COL_PROTOCOL:
|
case COL_PROTOCOL:
|
||||||
return tr("Protocol");
|
return tr("BT Protocol");
|
||||||
case COL_STATUS:
|
case COL_STATUS:
|
||||||
return tr("Status");
|
return tr("Status");
|
||||||
case COL_PEERS:
|
case COL_PEERS:
|
||||||
|
@ -506,9 +506,9 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
||||||
case COL_MSG:
|
case COL_MSG:
|
||||||
return tr("Message");
|
return tr("Message");
|
||||||
case COL_NEXT_ANNOUNCE:
|
case COL_NEXT_ANNOUNCE:
|
||||||
return tr("Next announce");
|
return tr("Next Announce");
|
||||||
case COL_MIN_ANNOUNCE:
|
case COL_MIN_ANNOUNCE:
|
||||||
return tr("Min announce");
|
return tr("Min Announce");
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -585,7 +585,7 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const
|
||||||
case COL_TIER:
|
case COL_TIER:
|
||||||
return (isEndpoint || (index.row() < STICKY_ROW_COUNT)) ? QString() : QString::number(itemPtr->tier);
|
return (isEndpoint || (index.row() < STICKY_ROW_COUNT)) ? QString() : QString::number(itemPtr->tier);
|
||||||
case COL_PROTOCOL:
|
case COL_PROTOCOL:
|
||||||
return isEndpoint ? tr("v%1").arg(itemPtr->btVersion) : QString();
|
return isEndpoint ? (u'v' + QString::number(itemPtr->btVersion)) : QString();
|
||||||
case COL_STATUS:
|
case COL_STATUS:
|
||||||
if (isEndpoint)
|
if (isEndpoint)
|
||||||
return toString(itemPtr->status);
|
return toString(itemPtr->status);
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/bittorrent/torrent.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
#include "base/bittorrent/trackerentrystatus.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_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_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_REANNOUNCE: return tr("Reannounce In", "Indicates the time until next trackers reannounce");
|
||||||
|
case TR_PRIVATE: return tr("Private", "Flags private torrents");
|
||||||
default: return {};
|
default: return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,6 +358,15 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
|
||||||
return Utils::Misc::userFriendlyDuration(time);
|
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)
|
switch (column)
|
||||||
{
|
{
|
||||||
case TR_NAME:
|
case TR_NAME:
|
||||||
|
@ -431,6 +441,8 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
|
||||||
return hashString(torrent->infoHash().v2());
|
return hashString(torrent->infoHash().v2());
|
||||||
case TR_REANNOUNCE:
|
case TR_REANNOUNCE:
|
||||||
return reannounceString(torrent->nextAnnounce());
|
return reannounceString(torrent->nextAnnounce());
|
||||||
|
case TR_PRIVATE:
|
||||||
|
return privateString(torrent->isPrivate(), torrent->hasMetadata());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -512,6 +524,8 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co
|
||||||
return QVariant::fromValue(torrent->infoHash().v2());
|
return QVariant::fromValue(torrent->infoHash().v2());
|
||||||
case TR_REANNOUNCE:
|
case TR_REANNOUNCE:
|
||||||
return torrent->nextAnnounce();
|
return torrent->nextAnnounce();
|
||||||
|
case TR_PRIVATE:
|
||||||
|
return (torrent->hasMetadata() ? torrent->isPrivate() : QVariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -86,6 +86,7 @@ public:
|
||||||
TR_INFOHASH_V1,
|
TR_INFOHASH_V1,
|
||||||
TR_INFOHASH_V2,
|
TR_INFOHASH_V2,
|
||||||
TR_REANNOUNCE,
|
TR_REANNOUNCE,
|
||||||
|
TR_PRIVATE,
|
||||||
|
|
||||||
NB_COLUMNS
|
NB_COLUMNS
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,8 +59,8 @@ namespace
|
||||||
int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare)
|
int customCompare(const TagSet &left, const TagSet &right, const Utils::Compare::NaturalCompare<Qt::CaseInsensitive> &compare)
|
||||||
{
|
{
|
||||||
for (auto leftIter = left.cbegin(), rightIter = right.cbegin();
|
for (auto leftIter = left.cbegin(), rightIter = right.cbegin();
|
||||||
(leftIter != left.cend()) && (rightIter != right.cend());
|
(leftIter != left.cend()) && (rightIter != right.cend());
|
||||||
++leftIter, ++rightIter)
|
++leftIter, ++rightIter)
|
||||||
{
|
{
|
||||||
const int result = compare(leftIter->toString(), rightIter->toString());
|
const int result = compare(leftIter->toString(), rightIter->toString());
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
|
@ -84,6 +84,17 @@ namespace
|
||||||
return isLeftValid ? -1 : 1;
|
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)
|
int adjustSubSortColumn(const int column)
|
||||||
{
|
{
|
||||||
return ((column >= 0) && (column < TransferListModel::NB_COLUMNS))
|
return ((column >= 0) && (column < TransferListModel::NB_COLUMNS))
|
||||||
|
@ -214,6 +225,9 @@ int TransferListSortModel::compare(const QModelIndex &left, const QModelIndex &r
|
||||||
case TransferListModel::TR_UPSPEED:
|
case TransferListModel::TR_UPSPEED:
|
||||||
return customCompare(leftValue.toInt(), rightValue.toInt());
|
return customCompare(leftValue.toInt(), rightValue.toInt());
|
||||||
|
|
||||||
|
case TransferListModel::TR_PRIVATE:
|
||||||
|
return compareAsBool(leftValue, rightValue);
|
||||||
|
|
||||||
case TransferListModel::TR_PEERS:
|
case TransferListModel::TR_PEERS:
|
||||||
case TransferListModel::TR_SEEDS:
|
case TransferListModel::TR_SEEDS:
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* 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)
|
void removeTorrents(const QVector<BitTorrent::Torrent *> &torrents, const bool isDeleteFileSelected)
|
||||||
{
|
{
|
||||||
auto *session = BitTorrent::Session::instance();
|
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)
|
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_LAST_ACTIVITY, true);
|
||||||
setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true);
|
setColumnHidden(TransferListModel::TR_TOTAL_SIZE, true);
|
||||||
setColumnHidden(TransferListModel::TR_REANNOUNCE, true);
|
setColumnHidden(TransferListModel::TR_REANNOUNCE, true);
|
||||||
|
setColumnHidden(TransferListModel::TR_PRIVATE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ensure that at least one column is visible at all times
|
//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
|
// Some torrents might be removed when waiting for user input, so refetch the torrent list
|
||||||
// NOTE: this will only work when dialog is modal
|
// NOTE: this will only work when dialog is modal
|
||||||
removeTorrents(getSelectedTorrents(), dialog->isDeleteFileSelected());
|
removeTorrents(getSelectedTorrents(), dialog->isRemoveContentSelected());
|
||||||
});
|
});
|
||||||
dialog->open();
|
dialog->open();
|
||||||
}
|
}
|
||||||
|
@ -465,7 +467,7 @@ void TransferListWidget::deleteVisibleTorrents()
|
||||||
{
|
{
|
||||||
// Some torrents might be removed when waiting for user input, so refetch the torrent list
|
// Some torrents might be removed when waiting for user input, so refetch the torrent list
|
||||||
// NOTE: this will only work when dialog is modal
|
// NOTE: this will only work when dialog is modal
|
||||||
removeTorrents(getVisibleTorrents(), dialog->isDeleteFileSelected());
|
removeTorrents(getVisibleTorrents(), dialog->isRemoveContentSelected());
|
||||||
});
|
});
|
||||||
dialog->open();
|
dialog->open();
|
||||||
}
|
}
|
||||||
|
@ -1190,7 +1192,7 @@ void TransferListWidget::displayListMenu()
|
||||||
const TagSet tags = BitTorrent::Session::instance()->tags();
|
const TagSet tags = BitTorrent::Session::instance()->tags();
|
||||||
for (const Tag &tag : asConst(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);
|
action->setCloseOnInteraction(false);
|
||||||
|
|
||||||
const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked
|
const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2017 Mike Tzou
|
* Copyright (C) 2017 Mike Tzou
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
|
#include "base/tag.h"
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/version.h"
|
#include "base/utils/version.h"
|
||||||
|
|
||||||
|
@ -216,3 +218,29 @@ void Utils::Gui::openFolderSelect(const Path &path)
|
||||||
openPath(path.parentPath());
|
openPath(path.parentPath());
|
||||||
#endif
|
#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.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2017 Mike Tzou
|
* Copyright (C) 2017 Mike Tzou
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -34,8 +35,11 @@ class QIcon;
|
||||||
class QPixmap;
|
class QPixmap;
|
||||||
class QPoint;
|
class QPoint;
|
||||||
class QSize;
|
class QSize;
|
||||||
|
class QString;
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
||||||
|
class Tag;
|
||||||
|
|
||||||
namespace Utils::Gui
|
namespace Utils::Gui
|
||||||
{
|
{
|
||||||
bool isDarkTheme();
|
bool isDarkTheme();
|
||||||
|
@ -51,4 +55,7 @@ namespace Utils::Gui
|
||||||
|
|
||||||
void openPath(const Path &path);
|
void openPath(const Path &path);
|
||||||
void openFolderSelect(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"?>
|
<?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">
|
<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) -->
|
<!-- Enable use of version 6 of the common controls (Win XP and later) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
|
@ -28,6 +35,7 @@
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</compatibility>
|
||||||
|
|
||||||
<!-- Enable long paths that exceed MAX_PATH in length -->
|
<!-- Enable long paths that exceed MAX_PATH in length -->
|
||||||
<asmv3:application>
|
<asmv3:application>
|
||||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#VERSION: 1.45
|
#VERSION: 1.47
|
||||||
|
|
||||||
# Author:
|
# Author:
|
||||||
# Christophe DUMEZ (chris@qbittorrent.org)
|
# Christophe DUMEZ (chris@qbittorrent.org)
|
||||||
|
@ -39,9 +39,11 @@ import tempfile
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
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 """
|
""" Disguise as browser to circumvent website blocking """
|
||||||
|
|
||||||
# Firefox release calendar
|
# 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"
|
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
|
# SOCKS5 Proxy support
|
||||||
if "sock_proxy" in os.environ and len(os.environ["sock_proxy"].strip()) > 0:
|
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:
|
if m is not None:
|
||||||
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, m.group('host'),
|
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, m.group('host'),
|
||||||
int(m.group('port')), True, m.group('username'), m.group('password'))
|
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 é)
|
# First convert alpha entities (such as é)
|
||||||
# (Inspired from http://mail.python.org/pipermail/python-list/2007-June/443813.html)
|
# (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)
|
entity = m.group(1)
|
||||||
if entity in html.entities.name2codepoint:
|
if entity in html.entities.name2codepoint:
|
||||||
return chr(html.entities.name2codepoint[entity])
|
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)
|
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 """
|
""" 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:
|
try:
|
||||||
response = urllib.request.urlopen(req)
|
response = urllib.request.urlopen(req)
|
||||||
except urllib.error.URLError as errno:
|
except urllib.error.URLError as errno:
|
||||||
print(" ".join(("Connection error:", str(errno.reason))))
|
print(" ".join(("Connection error:", str(errno.reason))))
|
||||||
return ""
|
return ""
|
||||||
dat = response.read()
|
dat: bytes = response.read()
|
||||||
# Check if it is gzipped
|
# Check if it is gzipped
|
||||||
if dat[:2] == b'\x1f\x8b':
|
if dat[:2] == b'\x1f\x8b':
|
||||||
# Data is gzip encoded, decode it
|
# Data is gzip encoded, decode it
|
||||||
|
@ -109,16 +111,15 @@ def retrieve_url(url):
|
||||||
ignore, charset = info['Content-Type'].split('charset=')
|
ignore, charset = info['Content-Type'].split('charset=')
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
dat = dat.decode(charset, 'replace')
|
datStr = dat.decode(charset, 'replace')
|
||||||
dat = htmlentitydecode(dat)
|
datStr = htmlentitydecode(datStr)
|
||||||
# return dat.encode('utf-8', 'replace')
|
return datStr
|
||||||
return dat
|
|
||||||
|
|
||||||
|
|
||||||
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 """
|
""" Download file at url and write it to a file, return the path to the file and the url """
|
||||||
file, path = tempfile.mkstemp()
|
fileHandle, path = tempfile.mkstemp()
|
||||||
file = os.fdopen(file, "wb")
|
file = os.fdopen(fileHandle, "wb")
|
||||||
# Download url
|
# Download url
|
||||||
req = urllib.request.Request(url, headers=headers)
|
req = urllib.request.Request(url, headers=headers)
|
||||||
if referer is not None:
|
if referer is not None:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#VERSION: 1.45
|
#VERSION: 1.46
|
||||||
|
|
||||||
# Author:
|
# Author:
|
||||||
# Fabien Devaux <fab AT gnux DOT info>
|
# Fabien Devaux <fab AT gnux DOT info>
|
||||||
|
@ -37,17 +37,21 @@ import importlib
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from collections.abc import Iterable, Iterator, Sequence
|
||||||
|
from enum import Enum
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from multiprocessing import Pool, cpu_count
|
from multiprocessing import Pool, cpu_count
|
||||||
from os import path
|
from os import path
|
||||||
|
from typing import Dict, List, Optional, Set, Tuple, Type
|
||||||
|
|
||||||
THREADED = True
|
THREADED: bool = True
|
||||||
try:
|
try:
|
||||||
MAX_THREADS = cpu_count()
|
MAX_THREADS: int = cpu_count()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
MAX_THREADS = 1
|
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
|
# 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
|
# global state
|
||||||
engine_dict = dict()
|
engine_dict: Dict[EngineName, Optional[Type[Engine]]] = {}
|
||||||
|
|
||||||
|
|
||||||
def list_engines():
|
def list_engines() -> List[EngineName]:
|
||||||
""" List all engines,
|
""" List all engines,
|
||||||
including broken engines that fail on import
|
including broken engines that fail on import
|
||||||
|
|
||||||
|
@ -81,10 +103,10 @@ def list_engines():
|
||||||
return found_engines
|
return found_engines
|
||||||
|
|
||||||
|
|
||||||
def get_engine(engine_name):
|
def get_engine(engine_name: EngineName) -> Optional[Type[Engine]]:
|
||||||
#global engine_dict
|
|
||||||
if engine_name in engine_dict:
|
if engine_name in engine_dict:
|
||||||
return engine_dict[engine_name]
|
return engine_dict[engine_name]
|
||||||
|
|
||||||
# when import fails, engine is None
|
# when import fails, engine is None
|
||||||
engine = None
|
engine = None
|
||||||
try:
|
try:
|
||||||
|
@ -97,35 +119,37 @@ def get_engine(engine_name):
|
||||||
return engine
|
return engine
|
||||||
|
|
||||||
|
|
||||||
def initialize_engines(found_engines):
|
def initialize_engines(found_engines: Iterable[EngineName]) -> Set[EngineName]:
|
||||||
""" Import available engines
|
""" Import available engines
|
||||||
|
|
||||||
Return list of available engines
|
Return set of available engines
|
||||||
"""
|
"""
|
||||||
supported_engines = []
|
supported_engines = set()
|
||||||
|
|
||||||
for engine_name in found_engines:
|
for engine_name in found_engines:
|
||||||
# import engine
|
# import engine
|
||||||
engine = get_engine(engine_name)
|
engine = get_engine(engine_name)
|
||||||
if engine is None:
|
if engine is None:
|
||||||
continue
|
continue
|
||||||
supported_engines.append(engine_name)
|
supported_engines.add(engine_name)
|
||||||
|
|
||||||
return supported_engines
|
return supported_engines
|
||||||
|
|
||||||
|
|
||||||
def engines_to_xml(supported_engines):
|
def engines_to_xml(supported_engines: Iterable[EngineName]) -> Iterator[str]:
|
||||||
""" Generates xml for supported engines """
|
""" Generates xml for supported engines """
|
||||||
tab = " " * 4
|
tab = " " * 4
|
||||||
|
|
||||||
for engine_name in supported_engines:
|
for engine_name in supported_engines:
|
||||||
search_engine = get_engine(engine_name)
|
search_engine = get_engine(engine_name)
|
||||||
|
if search_engine is None:
|
||||||
|
continue
|
||||||
|
|
||||||
supported_categories = ""
|
supported_categories = ""
|
||||||
if hasattr(search_engine, "supported_categories"):
|
if hasattr(search_engine, "supported_categories"):
|
||||||
supported_categories = " ".join((key
|
supported_categories = " ".join((key
|
||||||
for key in search_engine.supported_categories.keys()
|
for key in search_engine.supported_categories.keys()
|
||||||
if key != "all"))
|
if key != Category.all.name))
|
||||||
|
|
||||||
yield "".join((tab, "<", engine_name, ">\n",
|
yield "".join((tab, "<", engine_name, ">\n",
|
||||||
tab, tab, "<name>", search_engine.name, "</name>\n",
|
tab, tab, "<name>", search_engine.name, "</name>\n",
|
||||||
|
@ -134,7 +158,7 @@ def engines_to_xml(supported_engines):
|
||||||
tab, "</", engine_name, ">\n"))
|
tab, "</", engine_name, ">\n"))
|
||||||
|
|
||||||
|
|
||||||
def displayCapabilities(supported_engines):
|
def displayCapabilities(supported_engines: Iterable[EngineName]) -> None:
|
||||||
"""
|
"""
|
||||||
Display capabilities in XML format
|
Display capabilities in XML format
|
||||||
<capabilities>
|
<capabilities>
|
||||||
|
@ -151,21 +175,24 @@ def displayCapabilities(supported_engines):
|
||||||
print(xml)
|
print(xml)
|
||||||
|
|
||||||
|
|
||||||
def run_search(engine_list):
|
def run_search(engine_list: Tuple[Optional[Type[Engine]], str, Category]) -> bool:
|
||||||
""" Run search in engine
|
""" 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 False if any exceptions occurred
|
||||||
@retval True otherwise
|
@retval True otherwise
|
||||||
"""
|
"""
|
||||||
engine, what, cat = engine_list
|
engine_class, what, cat = engine_list
|
||||||
|
if engine_class is None:
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
engine = engine()
|
engine = engine_class()
|
||||||
# avoid exceptions due to invalid category
|
# avoid exceptions due to invalid category
|
||||||
if hasattr(engine, 'supported_categories'):
|
if hasattr(engine, 'supported_categories'):
|
||||||
if cat in engine.supported_categories:
|
if cat.name in engine.supported_categories:
|
||||||
engine.search(what, cat)
|
engine.search(what, cat.name)
|
||||||
else:
|
else:
|
||||||
engine.search(what)
|
engine.search(what)
|
||||||
|
|
||||||
|
@ -174,7 +201,7 @@ def run_search(engine_list):
|
||||||
return False
|
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
|
# qbt tend to run this script in 'isolate mode' so append the current path manually
|
||||||
current_path = str(pathlib.Path(__file__).parent.resolve())
|
current_path = str(pathlib.Path(__file__).parent.resolve())
|
||||||
if current_path not in sys.path:
|
if current_path not in sys.path:
|
||||||
|
@ -182,7 +209,7 @@ def main(args):
|
||||||
|
|
||||||
found_engines = list_engines()
|
found_engines = list_engines()
|
||||||
|
|
||||||
def show_usage():
|
def show_usage() -> None:
|
||||||
print("./nova2.py all|engine1[,engine2]* <category> <keywords>", file=sys.stderr)
|
print("./nova2.py all|engine1[,engine2]* <category> <keywords>", file=sys.stderr)
|
||||||
print("found engines: " + ','.join(found_engines), file=sys.stderr)
|
print("found engines: " + ','.join(found_engines), file=sys.stderr)
|
||||||
print("to list available engines: ./nova2.py --capabilities [--names]", 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:
|
if not args:
|
||||||
show_usage()
|
show_usage()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
elif args[0] == "--capabilities":
|
elif args[0] == "--capabilities":
|
||||||
supported_engines = initialize_engines(found_engines)
|
supported_engines = initialize_engines(found_engines)
|
||||||
if "--names" in args:
|
if "--names" in args:
|
||||||
|
@ -198,14 +224,14 @@ def main(args):
|
||||||
return
|
return
|
||||||
displayCapabilities(supported_engines)
|
displayCapabilities(supported_engines)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif len(args) < 3:
|
elif len(args) < 3:
|
||||||
show_usage()
|
show_usage()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
cat = args[1].lower()
|
cat = args[1].lower()
|
||||||
|
try:
|
||||||
if cat not in CATEGORIES:
|
category = Category[cat]
|
||||||
|
except KeyError:
|
||||||
print(" - ".join(('Invalid category', cat)), file=sys.stderr)
|
print(" - ".join(('Invalid category', cat)), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -223,16 +249,18 @@ def main(args):
|
||||||
engines_list = initialize_engines(found_engines)
|
engines_list = initialize_engines(found_engines)
|
||||||
else:
|
else:
|
||||||
# discard not-found engines
|
# 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:]))
|
what = urllib.parse.quote(' '.join(args[2:]))
|
||||||
|
params = ((get_engine(engine_name), what, category) for engine_name in engines_list)
|
||||||
|
|
||||||
if THREADED:
|
if THREADED:
|
||||||
# child process spawning is controlled min(number of searches, number of cpu)
|
# child process spawning is controlled min(number of searches, number of cpu)
|
||||||
with Pool(min(len(engines_list), MAX_THREADS)) as pool:
|
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:
|
else:
|
||||||
# py3 note: map is needed to be evaluated for content to be executed
|
# 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__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#VERSION: 1.48
|
#VERSION: 1.50
|
||||||
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
# modification, are permitted provided that the following conditions are met:
|
# 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
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# 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((
|
outtext = "|".join((
|
||||||
dictionary["link"],
|
dictionary["link"],
|
||||||
dictionary["name"].replace("|", " "),
|
dictionary["name"].replace("|", " "),
|
||||||
|
@ -34,7 +51,7 @@ def prettyPrinter(dictionary):
|
||||||
str(dictionary["leech"]),
|
str(dictionary["leech"]),
|
||||||
dictionary["engine_url"],
|
dictionary["engine_url"],
|
||||||
dictionary.get("desc_link", ""), # Optional
|
dictionary.get("desc_link", ""), # Optional
|
||||||
str(dictionary.get("pub_date", -1)), # Optional
|
str(dictionary.get("pub_date", -1)) # Optional
|
||||||
))
|
))
|
||||||
|
|
||||||
# fd 1 is stdout
|
# fd 1 is stdout
|
||||||
|
@ -42,30 +59,32 @@ def prettyPrinter(dictionary):
|
||||||
print(outtext, file=utf8stdout)
|
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)
|
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
|
The canonical type for `size_string` is `str`. However numeric types are also accepted in order to
|
||||||
units_dict = {'T': 40, 'G': 30, 'M': 20, 'K': 10}
|
accommodate poorly written plugins.
|
||||||
if short_unit in units_dict:
|
"""
|
||||||
size = size * 2**units_dict[short_unit]
|
|
||||||
return int(size)
|
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 <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
@ -135,7 +136,7 @@ void AppController::preferencesAction()
|
||||||
data[u"file_log_age"_s] = app()->fileLoggerAge();
|
data[u"file_log_age"_s] = app()->fileLoggerAge();
|
||||||
data[u"file_log_age_type"_s] = app()->fileLoggerAgeType();
|
data[u"file_log_age_type"_s] = app()->fileLoggerAgeType();
|
||||||
// Delete torrent contents files on torrent removal
|
// 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
|
// Downloads
|
||||||
// When adding a torrent
|
// When adding a torrent
|
||||||
|
@ -349,6 +350,8 @@ void AppController::preferencesAction()
|
||||||
// qBitorrent preferences
|
// qBitorrent preferences
|
||||||
// Resume data storage type
|
// Resume data storage type
|
||||||
data[u"resume_data_storage_type"_s] = Utils::String::fromEnum(session->resumeDataStorageType());
|
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
|
// Physical memory (RAM) usage limit
|
||||||
data[u"memory_working_set_limit"_s] = app()->memoryWorkingSetLimit();
|
data[u"memory_working_set_limit"_s] = app()->memoryWorkingSetLimit();
|
||||||
// Current network interface
|
// Current network interface
|
||||||
|
@ -518,7 +521,7 @@ void AppController::setPreferencesAction()
|
||||||
app()->setFileLoggerAgeType(it.value().toInt());
|
app()->setFileLoggerAgeType(it.value().toInt());
|
||||||
// Delete torrent content files on torrent removal
|
// Delete torrent content files on torrent removal
|
||||||
if (hasKey(u"delete_torrent_content_files"_s))
|
if (hasKey(u"delete_torrent_content_files"_s))
|
||||||
pref->setDeleteTorrentFilesAsDefault(it.value().toBool());
|
pref->setRemoveTorrentContent(it.value().toBool());
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
// When adding a torrent
|
// When adding a torrent
|
||||||
|
@ -930,6 +933,9 @@ void AppController::setPreferencesAction()
|
||||||
// Resume data storage type
|
// Resume data storage type
|
||||||
if (hasKey(u"resume_data_storage_type"_s))
|
if (hasKey(u"resume_data_storage_type"_s))
|
||||||
session->setResumeDataStorageType(Utils::String::toEnum(it.value().toString(), BitTorrent::ResumeDataStorageType::Legacy));
|
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
|
// Physical memory (RAM) usage limit
|
||||||
if (hasKey(u"memory_working_set_limit"_s))
|
if (hasKey(u"memory_working_set_limit"_s))
|
||||||
app()->setMemoryWorkingSetLimit(it.value().toInt());
|
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));
|
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));
|
QJsonArray ret;
|
||||||
setResult(QJsonArray::fromStringList(dirs));
|
QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
|
||||||
|
while (it.hasNext())
|
||||||
|
ret.append(it.next());
|
||||||
|
setResult(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppController::networkInterfaceListAction()
|
void AppController::networkInterfaceListAction()
|
||||||
|
|
|
@ -135,6 +135,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
|
||||||
{KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()},
|
{KEY_TORRENT_SAVE_PATH, torrent.savePath().toString()},
|
||||||
{KEY_TORRENT_DOWNLOAD_PATH, torrent.downloadPath().toString()},
|
{KEY_TORRENT_DOWNLOAD_PATH, torrent.downloadPath().toString()},
|
||||||
{KEY_TORRENT_CONTENT_PATH, torrent.contentPath().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_ADDED_ON, Utils::DateTime::toSecsSinceEpoch(torrent.addedTime())},
|
||||||
{KEY_TORRENT_COMPLETION_ON, Utils::DateTime::toSecsSinceEpoch(torrent.completedTime())},
|
{KEY_TORRENT_COMPLETION_ON, Utils::DateTime::toSecsSinceEpoch(torrent.completedTime())},
|
||||||
{KEY_TORRENT_TRACKER, torrent.currentTracker()},
|
{KEY_TORRENT_TRACKER, torrent.currentTracker()},
|
||||||
|
@ -163,8 +164,8 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
|
||||||
{KEY_TORRENT_AVAILABILITY, torrent.distributedCopies()},
|
{KEY_TORRENT_AVAILABILITY, torrent.distributedCopies()},
|
||||||
{KEY_TORRENT_REANNOUNCE, torrent.nextAnnounce()},
|
{KEY_TORRENT_REANNOUNCE, torrent.nextAnnounce()},
|
||||||
{KEY_TORRENT_COMMENT, torrent.comment()},
|
{KEY_TORRENT_COMMENT, torrent.comment()},
|
||||||
{KEY_TORRENT_ISPRIVATE, torrent.isPrivate()},
|
{KEY_TORRENT_PRIVATE, (torrent.hasMetadata() ? torrent.isPrivate() : QVariant())},
|
||||||
|
{KEY_TORRENT_TOTAL_SIZE, torrent.totalSize()},
|
||||||
{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_SAVE_PATH = u"save_path"_s;
|
||||||
inline const QString KEY_TORRENT_DOWNLOAD_PATH = u"download_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_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_ADDED_ON = u"added_on"_s;
|
||||||
inline const QString KEY_TORRENT_COMPLETION_ON = u"completion_on"_s;
|
inline const QString KEY_TORRENT_COMPLETION_ON = u"completion_on"_s;
|
||||||
inline const QString KEY_TORRENT_TRACKER = u"tracker"_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_AVAILABILITY = u"availability"_s;
|
||||||
inline const QString KEY_TORRENT_REANNOUNCE = u"reannounce"_s;
|
inline const QString KEY_TORRENT_REANNOUNCE = u"reannounce"_s;
|
||||||
inline const QString KEY_TORRENT_COMMENT = u"comment"_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);
|
QVariantMap serialize(const BitTorrent::Torrent &torrent);
|
||||||
|
|
|
@ -222,6 +222,7 @@ namespace
|
||||||
case QMetaType::UInt:
|
case QMetaType::UInt:
|
||||||
case QMetaType::QDateTime:
|
case QMetaType::QDateTime:
|
||||||
case QMetaType::Nullptr:
|
case QMetaType::Nullptr:
|
||||||
|
case QMetaType::UnknownType:
|
||||||
if (prevData[key] != value)
|
if (prevData[key] != value)
|
||||||
syncData[key] = value;
|
syncData[key] = value;
|
||||||
break;
|
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_SAVE_PATH = u"save_path"_s;
|
||||||
const QString KEY_PROP_DOWNLOAD_PATH = u"download_path"_s;
|
const QString KEY_PROP_DOWNLOAD_PATH = u"download_path"_s;
|
||||||
const QString KEY_PROP_COMMENT = u"comment"_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_CERTIFICATE = u"ssl_certificate"_s;
|
||||||
const QString KEY_PROP_SSL_PRIVATEKEY = u"ssl_private_key"_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_SSL_DHPARAMS = u"ssl_dh_params"_s;
|
||||||
|
const QString KEY_PROP_HAS_METADATA = u"has_metadata"_s;
|
||||||
|
|
||||||
|
|
||||||
// File keys
|
// File keys
|
||||||
const QString KEY_FILE_INDEX = u"index"_s;
|
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")
|
// - 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")
|
// - 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 |
|
// - 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
|
// - sort (string): name of column for sorting by its value
|
||||||
// - reverse (bool): enable reverse sorting
|
// - reverse (bool): enable reverse sorting
|
||||||
// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
|
// - 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 limit {params()[u"limit"_s].toInt()};
|
||||||
int offset {params()[u"offset"_s].toInt()};
|
int offset {params()[u"offset"_s].toInt()};
|
||||||
const QStringList hashes {params()[u"hashes"_s].split(u'|', Qt::SkipEmptyParts)};
|
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;
|
std::optional<TorrentIDSet> idSet;
|
||||||
if (!hashes.isEmpty())
|
if (!hashes.isEmpty())
|
||||||
|
@ -305,7 +310,7 @@ void TorrentsController::infoAction()
|
||||||
idSet->insert(BitTorrent::TorrentID::fromString(hash));
|
idSet->insert(BitTorrent::TorrentID::fromString(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
const TorrentFilter torrentFilter {filter, idSet, category, tag};
|
const TorrentFilter torrentFilter {filter, idSet, category, tag, isPrivate};
|
||||||
QVariantList torrentList;
|
QVariantList torrentList;
|
||||||
for (const BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
for (const BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||||
{
|
{
|
||||||
|
@ -435,6 +440,8 @@ void TorrentsController::propertiesAction()
|
||||||
const int uploadLimit = torrent->uploadLimit();
|
const int uploadLimit = torrent->uploadLimit();
|
||||||
const qreal ratio = torrent->realRatio();
|
const qreal ratio = torrent->realRatio();
|
||||||
const qreal popularity = torrent->popularity();
|
const qreal popularity = torrent->popularity();
|
||||||
|
const bool hasMetadata = torrent->hasMetadata();
|
||||||
|
const bool isPrivate = torrent->isPrivate();
|
||||||
|
|
||||||
const QJsonObject ret
|
const QJsonObject ret
|
||||||
{
|
{
|
||||||
|
@ -470,14 +477,16 @@ void TorrentsController::propertiesAction()
|
||||||
{KEY_PROP_PIECE_SIZE, torrent->pieceLength()},
|
{KEY_PROP_PIECE_SIZE, torrent->pieceLength()},
|
||||||
{KEY_PROP_PIECES_HAVE, torrent->piecesHave()},
|
{KEY_PROP_PIECES_HAVE, torrent->piecesHave()},
|
||||||
{KEY_PROP_CREATED_BY, torrent->creator()},
|
{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_ADDITION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->addedTime())},
|
||||||
{KEY_PROP_LAST_SEEN, Utils::DateTime::toSecsSinceEpoch(torrent->lastSeenComplete())},
|
{KEY_PROP_LAST_SEEN, Utils::DateTime::toSecsSinceEpoch(torrent->lastSeenComplete())},
|
||||||
{KEY_PROP_COMPLETION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->completedTime())},
|
{KEY_PROP_COMPLETION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->completedTime())},
|
||||||
{KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->creationDate())},
|
{KEY_PROP_CREATION_DATE, Utils::DateTime::toSecsSinceEpoch(torrent->creationDate())},
|
||||||
{KEY_PROP_SAVE_PATH, torrent->savePath().toString()},
|
{KEY_PROP_SAVE_PATH, torrent->savePath().toString()},
|
||||||
{KEY_PROP_DOWNLOAD_PATH, torrent->downloadPath().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);
|
setResult(ret);
|
||||||
|
@ -1092,11 +1101,11 @@ void TorrentsController::deleteAction()
|
||||||
requireParams({u"hashes"_s, u"deleteFiles"_s});
|
requireParams({u"hashes"_s, u"deleteFiles"_s});
|
||||||
|
|
||||||
const QStringList hashes {params()[u"hashes"_s].split(u'|')};
|
const QStringList hashes {params()[u"hashes"_s].split(u'|')};
|
||||||
const DeleteOption deleteOption = parseBool(params()[u"deleteFiles"_s]).value_or(false)
|
const BitTorrent::TorrentRemoveOption deleteOption = parseBool(params()[u"deleteFiles"_s]).value_or(false)
|
||||||
? DeleteTorrentAndFiles : DeleteTorrent;
|
? BitTorrent::TorrentRemoveOption::RemoveContent : BitTorrent::TorrentRemoveOption::KeepContent;
|
||||||
applyToTorrents(hashes, [deleteOption](const BitTorrent::Torrent *torrent)
|
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);
|
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, syncController, &SyncController::updateFreeDiskSpace);
|
||||||
m_currentSession->registerAPIController(u"sync"_s, syncController);
|
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.setHttpOnly(true);
|
||||||
cookie.setSecure(m_isSecureCookieEnabled && m_isHttpsEnabled);
|
cookie.setSecure(m_isSecureCookieEnabled && m_isHttpsEnabled);
|
||||||
cookie.setPath(u"/"_s);
|
cookie.setPath(u"/"_s);
|
||||||
QByteArray cookieRawForm = cookie.toRawForm();
|
|
||||||
if (m_isCSRFProtectionEnabled)
|
if (m_isCSRFProtectionEnabled)
|
||||||
cookieRawForm.append("; SameSite=Strict");
|
cookie.setSameSitePolicy(QNetworkCookie::SameSite::Strict);
|
||||||
else if (cookie.isSecure())
|
else if (cookie.isSecure())
|
||||||
cookieRawForm.append("; SameSite=None");
|
cookie.setSameSitePolicy(QNetworkCookie::SameSite::None);
|
||||||
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookieRawForm)});
|
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookie.toRawForm())});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApplication::sessionEnd()
|
void WebApplication::sessionEnd()
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
#include "base/utils/version.h"
|
#include "base/utils/version.h"
|
||||||
#include "api/isessionmanager.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;
|
class QTimer;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Globals from 'globals';
|
import Globals from "globals";
|
||||||
import Html from 'eslint-plugin-html';
|
import Html from "eslint-plugin-html";
|
||||||
import Js from '@eslint/js';
|
import Js from "@eslint/js";
|
||||||
import Stylistic from '@stylistic/eslint-plugin';
|
import Stylistic from "@stylistic/eslint-plugin";
|
||||||
import * as RegexpPlugin from 'eslint-plugin-regexp';
|
import * as RegexpPlugin from "eslint-plugin-regexp";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
Js.configs.recommended,
|
Js.configs.recommended,
|
||||||
|
@ -26,9 +26,16 @@ export default [
|
||||||
Stylistic
|
Stylistic
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"curly": ["error", "multi-or-nest", "consistent"],
|
||||||
"eqeqeq": "error",
|
"eqeqeq": "error",
|
||||||
|
"guard-for-in": "error",
|
||||||
"no-undef": "off",
|
"no-undef": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"operator-assignment": "error",
|
||||||
|
"prefer-arrow-callback": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"radix": "error",
|
||||||
"Stylistic/no-mixed-operators": [
|
"Stylistic/no-mixed-operators": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
@ -38,7 +45,16 @@ export default [
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Stylistic/nonblock-statement-body-position": ["error", "below"],
|
"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"
|
"url": "https://github.com/qbittorrent/qBittorrent.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "js-beautify -r private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && prettier --write **.css",
|
"format": "js-beautify -r *.mjs 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"
|
"lint": "eslint *.mjs private/*.html private/scripts/*.js private/views/*.html public/*.html public/scripts/*.js && stylelint **/*.css && html-validate private public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "*",
|
"@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-Core-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
const hash = new URI().getData('hash');
|
const hash = new URI().getData("hash");
|
||||||
if (!hash)
|
if (!hash)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$('peers').focus();
|
$("peers").focus();
|
||||||
|
|
||||||
$('addPeersOk').addEvent('click', function(e) {
|
$("addPeersOk").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
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)
|
if (peers.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/addPeers',
|
url: "api/v2/torrents/addPeers",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hash,
|
hashes: hash,
|
||||||
peers: peers.join('|')
|
peers: peers.join("|")
|
||||||
},
|
},
|
||||||
onFailure: function() {
|
onFailure: function() {
|
||||||
alert("QBT_TR(Unable to add peers. Please ensure you are adhering to the IP:port format.)QBT_TR[CONTEXT=HttpServer]");
|
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-Core-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
$('trackersUrls').focus();
|
$("trackersUrls").focus();
|
||||||
$('addTrackersButton').addEvent('click', function(e) {
|
$("addTrackersButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const hash = new URI().getData('hash');
|
const hash = new URI().getData("hash");
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/addTrackers',
|
url: "api/v2/torrents/addTrackers",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
urls: $('trackersUrls').value
|
urls: $("trackersUrls").value
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
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-Core-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
function setRememberBtnEnabled(enable) {
|
function setRememberBtnEnabled(enable) {
|
||||||
const btn = $('rememberBtn');
|
const btn = $("rememberBtn");
|
||||||
btn.disabled = !enable;
|
btn.disabled = !enable;
|
||||||
|
|
||||||
const icon = btn.getElementsByTagName('path')[0];
|
const icon = btn.getElementsByTagName("path")[0];
|
||||||
if (enable)
|
if (enable)
|
||||||
icon.style.removeProperty('fill');
|
icon.style.removeProperty("fill");
|
||||||
else
|
else
|
||||||
icon.style.fill = "var(--color-border-default)";
|
icon.style.fill = "var(--color-border-default)";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'images/object-locked.svg',
|
url: "images/object-locked.svg",
|
||||||
method: 'get',
|
method: "get",
|
||||||
onSuccess: function(text, xml) {
|
onSuccess: function(text, xml) {
|
||||||
const newIcon = xml.childNodes[0];
|
const newIcon = xml.childNodes[0];
|
||||||
newIcon.style.height = '24px';
|
newIcon.style.height = "24px";
|
||||||
newIcon.style.width = '24px';
|
newIcon.style.width = "24px";
|
||||||
$('rememberBtn').appendChild(newIcon);
|
$("rememberBtn").appendChild(newIcon);
|
||||||
setRememberBtnEnabled(false);
|
setRememberBtnEnabled(false);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
|
||||||
const isDeletingFiles = (new URI().getData('deleteFiles') === "true");
|
const isDeletingFiles = (new URI().getData("deleteFiles") === "true");
|
||||||
$('deleteFromDiskCB').checked = isDeletingFiles;
|
$("deleteFromDiskCB").checked = isDeletingFiles;
|
||||||
|
|
||||||
const prefCache = window.parent.qBittorrent.Cache.preferences.get();
|
const prefCache = window.parent.qBittorrent.Cache.preferences.get();
|
||||||
let prefDeleteContentFiles = prefCache.delete_torrent_content_files;
|
let prefDeleteContentFiles = prefCache.delete_torrent_content_files;
|
||||||
|
|
||||||
$('deleteFromDiskCB').checked ||= prefDeleteContentFiles;
|
$("deleteFromDiskCB").checked ||= prefDeleteContentFiles;
|
||||||
$('deleteFromDiskCB').addEvent('click', function(e) {
|
$("deleteFromDiskCB").addEvent("click", (e) => {
|
||||||
setRememberBtnEnabled($('deleteFromDiskCB').checked !== prefDeleteContentFiles);
|
setRememberBtnEnabled($("deleteFromDiskCB").checked !== prefDeleteContentFiles);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set current "Delete files" choice as the default
|
// Set current "Delete files" choice as the default
|
||||||
$('rememberBtn').addEvent('click', function(e) {
|
$("rememberBtn").addEvent("click", (e) => {
|
||||||
window.parent.qBittorrent.Cache.preferences.set({
|
window.parent.qBittorrent.Cache.preferences.set({
|
||||||
data: {
|
data: {
|
||||||
'delete_torrent_content_files': $('deleteFromDiskCB').checked
|
"delete_torrent_content_files": $("deleteFromDiskCB").checked
|
||||||
},
|
},
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
prefDeleteContentFiles = $('deleteFromDiskCB').checked;
|
prefDeleteContentFiles = $("deleteFromDiskCB").checked;
|
||||||
setRememberBtnEnabled(false);
|
setRememberBtnEnabled(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const hashes = new URI().getData('hashes').split('|');
|
const hashes = new URI().getData("hashes").split("|");
|
||||||
$('cancelBtn').focus();
|
$("cancelBtn").focus();
|
||||||
$('cancelBtn').addEvent('click', function(e) {
|
$("cancelBtn").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
});
|
});
|
||||||
$('confirmBtn').addEvent('click', function(e) {
|
$("confirmBtn").addEvent("click", (e) => {
|
||||||
parent.torrentsTable.deselectAll();
|
parent.torrentsTable.deselectAll();
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const cmd = 'api/v2/torrents/delete';
|
const cmd = "api/v2/torrents/delete";
|
||||||
const deleteFiles = $('deleteFromDiskCB').get('checked');
|
const deleteFiles = $("deleteFromDiskCB").get("checked");
|
||||||
new Request({
|
new Request({
|
||||||
url: cmd,
|
url: cmd,
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
'hashes': hashes.join('|'),
|
"hashes": hashes.join("|"),
|
||||||
'deleteFiles': deleteFiles
|
"deleteFiles": deleteFiles
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
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>
|
<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 id="rememberBtn" type="button" title="Remember choice" style="vertical-align: middle; padding: 4px 6px;">
|
||||||
</button>
|
</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;">
|
<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]" />
|
<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>
|
</div>
|
||||||
|
|
|
@ -8,22 +8,22 @@
|
||||||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
<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 src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
const paths = new URI().getData('paths').split('|');
|
const paths = new URI().getData("paths").split("|");
|
||||||
$('cancelBtn').focus();
|
$("cancelBtn").focus();
|
||||||
$('cancelBtn').addEvent('click', (e) => {
|
$("cancelBtn").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
});
|
});
|
||||||
$('confirmBtn').addEvent('click', (e) => {
|
$("confirmBtn").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
let completionCount = 0;
|
let completionCount = 0;
|
||||||
paths.forEach((path) => {
|
paths.forEach((path) => {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/removeItem',
|
url: "api/v2/rss/removeItem",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
path: decodeURIComponent(path)
|
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-Core-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
const rules = new URI().getData('rules').split('|');
|
const rules = new URI().getData("rules").split("|");
|
||||||
|
|
||||||
$('cancelBtn').focus();
|
$("cancelBtn").focus();
|
||||||
$('cancelBtn').addEvent('click', (e) => {
|
$("cancelBtn").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
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();
|
new Event(e).stop();
|
||||||
let completionCount = 0;
|
let completionCount = 0;
|
||||||
rules.forEach((rule) => {
|
rules.forEach((rule) => {
|
||||||
window.parent.qBittorrent.RssDownloader.modifyRuleState(decodeURIComponent(rule), 'previouslyMatchedEpisodes', [], () => {
|
window.parent.qBittorrent.RssDownloader.modifyRuleState(decodeURIComponent(rule), "previouslyMatchedEpisodes", [], () => {
|
||||||
++completionCount;
|
++completionCount;
|
||||||
if (completionCount === rules.length) {
|
if (completionCount === rules.length) {
|
||||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
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-Core-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
const rules = new URI().getData('rules').split('|');
|
const rules = new URI().getData("rules").split("|");
|
||||||
|
|
||||||
$('cancelBtn').focus();
|
$("cancelBtn").focus();
|
||||||
$('cancelBtn').addEvent('click', (e) => {
|
$("cancelBtn").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
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();
|
new Event(e).stop();
|
||||||
let completionCount = 0;
|
let completionCount = 0;
|
||||||
rules.forEach((rule) => {
|
rules.forEach((rule) => {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/removeRule',
|
url: "api/v2/rss/removeRule",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
ruleName: decodeURIComponent(rule)
|
ruleName: decodeURIComponent(rule)
|
||||||
},
|
},
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
++completionCount;
|
++completionCount;
|
||||||
if (completionCount === rules.length) {
|
if (completionCount === rules.length) {
|
||||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('removeRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("removeRulePage"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
|
|
@ -163,31 +163,31 @@
|
||||||
<div id="download_spinner" class="mochaSpinner"></div>
|
<div id="download_spinner" class="mochaSpinner"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const encodedUrls = new URI().getData('urls');
|
const encodedUrls = new URI().getData("urls");
|
||||||
if (encodedUrls) {
|
if (encodedUrls) {
|
||||||
const urls = encodedUrls.split('|').map(function(url) {
|
const urls = encodedUrls.split("|").map((url) => {
|
||||||
return decodeURIComponent(url);
|
return decodeURIComponent(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (urls.length)
|
if (urls.length)
|
||||||
$('urls').set('value', urls.join("\n"));
|
$("urls").set("value", urls.join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let submitted = false;
|
let submitted = false;
|
||||||
|
|
||||||
$('downloadForm').addEventListener("submit", function() {
|
$("downloadForm").addEventListener("submit", () => {
|
||||||
$('startTorrentHidden').value = $('startTorrent').checked ? 'false' : 'true';
|
$("startTorrentHidden").value = $("startTorrent").checked ? "false" : "true";
|
||||||
|
|
||||||
$('dlLimitHidden').value = $('dlLimitText').value.toInt() * 1024;
|
$("dlLimitHidden").value = $("dlLimitText").value.toInt() * 1024;
|
||||||
$('upLimitHidden').value = $('upLimitText').value.toInt() * 1024;
|
$("upLimitHidden").value = $("upLimitText").value.toInt() * 1024;
|
||||||
|
|
||||||
$('download_spinner').style.display = "block";
|
$("download_spinner").style.display = "block";
|
||||||
submitted = true;
|
submitted = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
$('download_frame').addEventListener("load", function() {
|
$("download_frame").addEventListener("load", () => {
|
||||||
if (submitted)
|
if (submitted)
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,17 +25,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const hashes = new URI().getData('hashes').split('|');
|
const hashes = new URI().getData("hashes").split("|");
|
||||||
const setDlLimit = function() {
|
const setDlLimit = function() {
|
||||||
const limit = $("dllimitUpdatevalue").value.toInt() * 1024;
|
const limit = $("dllimitUpdatevalue").value.toInt() * 1024;
|
||||||
if (hashes[0] === "global") {
|
if (hashes[0] === "global") {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/transfer/setDownloadLimit',
|
url: "api/v2/transfer/setDownloadLimit",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
'limit': limit
|
"limit": limit
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
window.parent.updateMainData();
|
window.parent.updateMainData();
|
||||||
|
@ -45,11 +45,11 @@
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setDownloadLimit',
|
url: "api/v2/torrents/setDownloadLimit",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
'hashes': hashes.join('|'),
|
"hashes": hashes.join("|"),
|
||||||
'limit': limit
|
"limit": limit
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
|
@ -59,24 +59,24 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('applyButton').click();
|
$("applyButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
$('dllimitUpdatevalue').focus();
|
$("dllimitUpdatevalue").focus();
|
||||||
|
|
||||||
MochaUI.addDlLimitSlider(hashes);
|
MochaUI.addDlLimitSlider(hashes);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,44 +8,44 @@
|
||||||
<script src="scripts/lib/MooTools-Core-1.6.0-compat-compressed.js"></script>
|
<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 src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('editTrackerButton').click();
|
$("editTrackerButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
const currentUrl = new URI().getData('url');
|
const currentUrl = new URI().getData("url");
|
||||||
if (!currentUrl)
|
if (!currentUrl)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$('trackerUrl').value = currentUrl;
|
$("trackerUrl").value = currentUrl;
|
||||||
$('trackerUrl').focus();
|
$("trackerUrl").focus();
|
||||||
|
|
||||||
$('editTrackerButton').addEvent('click', function(e) {
|
$("editTrackerButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const hash = new URI().getData('hash');
|
const hash = new URI().getData("hash");
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/editTracker',
|
url: "api/v2/torrents/editTracker",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
origUrl: currentUrl,
|
origUrl: currentUrl,
|
||||||
newUrl: $('trackerUrl').value
|
newUrl: $("trackerUrl").value
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
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/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('categoryNameButton').click();
|
$("categoryNameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action'));
|
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData("action"));
|
||||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes'));
|
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData("hashes"));
|
||||||
const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData('categoryName'));
|
const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData("categoryName"));
|
||||||
const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData('savePath'));
|
const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData("savePath"));
|
||||||
|
|
||||||
if (uriAction === "edit") {
|
if (uriAction === "edit") {
|
||||||
if (!uriCategoryName)
|
if (!uriCategoryName)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$('categoryName').set('disabled', true);
|
$("categoryName").set("disabled", true);
|
||||||
$('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
$("categoryName").set("value", window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||||
$('savePath').set('value', window.qBittorrent.Misc.escapeHtml(uriSavePath));
|
$("savePath").set("value", window.qBittorrent.Misc.escapeHtml(uriSavePath));
|
||||||
$('savePath').focus();
|
$("savePath").focus();
|
||||||
}
|
}
|
||||||
else if (uriAction === "createSubcategory") {
|
else if (uriAction === "createSubcategory") {
|
||||||
$('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
$("categoryName").set("value", window.qBittorrent.Misc.escapeHtml(uriCategoryName));
|
||||||
$('categoryName').focus();
|
$("categoryName").focus();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('categoryName').focus();
|
$("categoryName").focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('categoryNameButton').addEvent('click', function(e) {
|
$("categoryNameButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
|
|
||||||
const savePath = $('savePath').value.trim();
|
const savePath = $("savePath").value.trim();
|
||||||
const categoryName = $('categoryName').value.trim();
|
const categoryName = $("categoryName").value.trim();
|
||||||
|
|
||||||
const verifyCategoryName = function(name) {
|
const verifyCategoryName = function(name) {
|
||||||
if ((name === null) || (name === ""))
|
if ((name === null) || (name === ""))
|
||||||
|
@ -74,16 +74,16 @@
|
||||||
return;
|
return;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/createCategory',
|
url: "api/v2/torrents/createCategory",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
category: categoryName,
|
category: categoryName,
|
||||||
savePath: savePath
|
savePath: savePath
|
||||||
},
|
},
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setCategory',
|
url: "api/v2/torrents/setCategory",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: uriHashes,
|
hashes: uriHashes,
|
||||||
category: categoryName
|
category: categoryName
|
||||||
|
@ -104,8 +104,8 @@
|
||||||
return;
|
return;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/createCategory',
|
url: "api/v2/torrents/createCategory",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
category: categoryName,
|
category: categoryName,
|
||||||
savePath: savePath
|
savePath: savePath
|
||||||
|
@ -117,8 +117,8 @@
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/editCategory',
|
url: "api/v2/torrents/editCategory",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
category: uriCategoryName, // category name can't be changed
|
category: uriCategoryName, // category name can't be changed
|
||||||
savePath: savePath
|
savePath: savePath
|
||||||
|
|
|
@ -9,45 +9,45 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': (event) => {
|
"Enter": (event) => {
|
||||||
$('submitButton').click();
|
$("submitButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': (event) => {
|
"Escape": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': (event) => {
|
"Esc": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
$('feedURL').focus();
|
$("feedURL").focus();
|
||||||
const path = new URI().getData('path');
|
const path = new URI().getData("path");
|
||||||
$('submitButton').addEvent('click', (e) => {
|
$("submitButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const feedURL = $('feedURL').value.trim();
|
const feedURL = $("feedURL").value.trim();
|
||||||
if (feedURL === '') {
|
if (feedURL === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('submitButton').disabled = true;
|
$("submitButton").disabled = true;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/addFeed',
|
url: "api/v2/rss/addFeed",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
url: feedURL,
|
url: feedURL,
|
||||||
path: path ? (path + '\\' + feedURL) : ''
|
path: path ? (path + "\\" + feedURL) : ""
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
onFailure: (response) => {
|
onFailure: (response) => {
|
||||||
if (response.status === 409)
|
if (response.status === 409)
|
||||||
alert(response.responseText);
|
alert(response.responseText);
|
||||||
$('submitButton').disabled = false;
|
$("submitButton").disabled = false;
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,44 +9,44 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': (event) => {
|
"Enter": (event) => {
|
||||||
$('submitButton').click();
|
$("submitButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': (event) => {
|
"Escape": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': (event) => {
|
"Esc": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
$('folderName').focus();
|
$("folderName").focus();
|
||||||
const path = new URI().getData('path');
|
const path = new URI().getData("path");
|
||||||
$('submitButton').addEvent('click', (e) => {
|
$("submitButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const folderName = $('folderName').value.trim();
|
const folderName = $("folderName").value.trim();
|
||||||
if (folderName === '') {
|
if (folderName === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('submitButton').disabled = true;
|
$("submitButton").disabled = true;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/addFolder',
|
url: "api/v2/rss/addFolder",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
path: path ? (path + '\\' + folderName) : folderName
|
path: path ? (path + "\\" + folderName) : folderName
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
window.parent.qBittorrent.Rss.updateRssFeedList();
|
window.parent.qBittorrent.Rss.updateRssFeedList();
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
onFailure: (response) => {
|
onFailure: (response) => {
|
||||||
if (response.status === 409)
|
if (response.status === 409)
|
||||||
alert(response.responseText);
|
alert(response.responseText);
|
||||||
$('submitButton').disabled = false;
|
$("submitButton").disabled = false;
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,46 +9,46 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': (event) => {
|
"Enter": (event) => {
|
||||||
$('submitButton').click();
|
$("submitButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': (event) => {
|
"Escape": (event) => {
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': (event) => {
|
"Esc": (event) => {
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
$('name').focus();
|
$("name").focus();
|
||||||
$('submitButton').addEvent('click', (e) => {
|
$("submitButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const name = $('name').value.trim();
|
const name = $("name").value.trim();
|
||||||
if (name === '') {
|
if (name === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$('submitButton').disabled = true;
|
$("submitButton").disabled = true;
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/setRule',
|
url: "api/v2/rss/setRule",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
ruleName: name,
|
ruleName: name,
|
||||||
ruleDef: '{}'
|
ruleDef: "{}"
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('newRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("newRulePage"));
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,39 +9,39 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('tagNameButton').click();
|
$("tagNameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action'));
|
const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData("action"));
|
||||||
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes'));
|
const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData("hashes"));
|
||||||
|
|
||||||
if (uriAction === 'create')
|
if (uriAction === "create")
|
||||||
$('legendText').innerText = 'QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]';
|
$("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();
|
new Event(e).stop();
|
||||||
|
|
||||||
const tagName = $('tagName').value.trim();
|
const tagName = $("tagName").value.trim();
|
||||||
|
|
||||||
const verifyTagName = function(name) {
|
const verifyTagName = function(name) {
|
||||||
if ((name === null) || (name === ""))
|
if ((name === null) || (name === ""))
|
||||||
|
@ -59,8 +59,8 @@
|
||||||
return;
|
return;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/addTags',
|
url: "api/v2/torrents/addTags",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: uriHashes,
|
hashes: uriHashes,
|
||||||
tags: tagName,
|
tags: tagName,
|
||||||
|
@ -76,8 +76,8 @@
|
||||||
return;
|
return;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/createTags',
|
url: "api/v2/torrents/createTags",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
tags: tagName,
|
tags: tagName,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,45 +9,45 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('renameButton').click();
|
$("renameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
const name = new URI().getData('name');
|
const name = new URI().getData("name");
|
||||||
// set text field to current value
|
// set text field to current value
|
||||||
if (name)
|
if (name)
|
||||||
$('rename').value = name;
|
$("rename").value = name;
|
||||||
|
|
||||||
$('rename').focus();
|
$("rename").focus();
|
||||||
$('renameButton').addEvent('click', function(e) {
|
$("renameButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const name = $('rename').value.trim();
|
const name = $("rename").value.trim();
|
||||||
if ((name === null) || (name === ""))
|
if ((name === null) || (name === ""))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const hash = new URI().getData('hash');
|
const hash = new URI().getData("hash");
|
||||||
if (hash) {
|
if (hash) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/rename',
|
url: "api/v2/torrents/rename",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
name: name
|
name: name
|
||||||
|
|
|
@ -9,51 +9,51 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': (event) => {
|
"Enter": (event) => {
|
||||||
$('renameButton').click();
|
$("renameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': (event) => {
|
"Escape": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': (event) => {
|
"Esc": (event) => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
const oldPath = new URI().getData('oldPath');
|
const oldPath = new URI().getData("oldPath");
|
||||||
|
|
||||||
$('rename').value = oldPath;
|
$("rename").value = oldPath;
|
||||||
$('rename').focus();
|
$("rename").focus();
|
||||||
$('rename').setSelectionRange(0, oldPath.length);
|
$("rename").setSelectionRange(0, oldPath.length);
|
||||||
|
|
||||||
$('renameButton').addEvent('click', (e) => {
|
$("renameButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const newPath = $('rename').value.trim();
|
const newPath = $("rename").value.trim();
|
||||||
if (newPath === '') {
|
if (newPath === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPath === oldPath) {
|
if (newPath === oldPath) {
|
||||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('renameButton').disabled = true;
|
$("renameButton").disabled = true;
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/moveItem',
|
url: "api/v2/rss/moveItem",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
itemPath: oldPath,
|
itemPath: oldPath,
|
||||||
destPath: newPath
|
destPath: newPath
|
||||||
|
@ -63,10 +63,9 @@
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
},
|
},
|
||||||
onFailure: (response) => {
|
onFailure: (response) => {
|
||||||
if (response.status === 409) {
|
if (response.status === 409)
|
||||||
alert(response.responseText);
|
alert(response.responseText);
|
||||||
}
|
$("renameButton").disabled = false;
|
||||||
$('renameButton').disabled = false;
|
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,60 +10,60 @@
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
<script src="scripts/filesystem.js?v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': function(event) {
|
"Enter": function(event) {
|
||||||
$('renameButton').click();
|
$("renameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
|
|
||||||
window.addEvent('domready', function() {
|
window.addEvent("domready", () => {
|
||||||
const hash = new URI().getData('hash');
|
const hash = new URI().getData("hash");
|
||||||
const oldPath = new URI().getData('path');
|
const oldPath = new URI().getData("path");
|
||||||
const isFolder = ((new URI().getData('isFolder')) === 'true');
|
const isFolder = ((new URI().getData("isFolder")) === "true");
|
||||||
|
|
||||||
const oldName = window.qBittorrent.Filesystem.fileName(oldPath);
|
const oldName = window.qBittorrent.Filesystem.fileName(oldPath);
|
||||||
$('rename').value = oldName;
|
$("rename").value = oldName;
|
||||||
$('rename').focus();
|
$("rename").focus();
|
||||||
if (!isFolder)
|
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();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const newName = $('rename').value.trim();
|
const newName = $("rename").value.trim();
|
||||||
if (newName === '') {
|
if (newName === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newName === oldName) {
|
if (newName === oldName) {
|
||||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('renameButton').disabled = true;
|
$("renameButton").disabled = true;
|
||||||
|
|
||||||
const parentPath = window.qBittorrent.Filesystem.folderName(oldPath);
|
const parentPath = window.qBittorrent.Filesystem.folderName(oldPath);
|
||||||
const newPath = parentPath
|
const newPath = parentPath
|
||||||
? parentPath + window.qBittorrent.Filesystem.PathSeparator + newName
|
? parentPath + window.qBittorrent.Filesystem.PathSeparator + newName
|
||||||
: newName;
|
: newName;
|
||||||
new Request({
|
new Request({
|
||||||
url: isFolder ? 'api/v2/torrents/renameFolder' : 'api/v2/torrents/renameFile',
|
url: isFolder ? "api/v2/torrents/renameFolder" : "api/v2/torrents/renameFile",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hash: hash,
|
hash: hash,
|
||||||
oldPath: oldPath,
|
oldPath: oldPath,
|
||||||
|
@ -73,8 +73,8 @@
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
},
|
},
|
||||||
onFailure: function() {
|
onFailure: function() {
|
||||||
alert('QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Failed to update name)QBT_TR[CONTEXT=HttpServer]");
|
||||||
$('renameButton').disabled = false;
|
$("renameButton").disabled = false;
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,23 +12,22 @@
|
||||||
<script src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/dynamicTable.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script src="scripts/rename-files.js?v=${CACHEID}"></script>
|
<script src="scripts/rename-files.js?v=${CACHEID}"></script>
|
||||||
<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;
|
||||||
}
|
|
||||||
window.qBittorrent = window.parent.qBittorrent;
|
window.qBittorrent = window.parent.qBittorrent;
|
||||||
|
|
||||||
var TriState = window.qBittorrent.FileTree.TriState;
|
const TriState = window.qBittorrent.FileTree.TriState;
|
||||||
var data = window.MUI.Windows.instances['multiRenamePage'].options.data;
|
const data = window.MUI.Windows.instances["multiRenamePage"].options.data;
|
||||||
var bulkRenameFilesContextMenu;
|
let bulkRenameFilesContextMenu;
|
||||||
if (!bulkRenameFilesContextMenu) {
|
if (!bulkRenameFilesContextMenu) {
|
||||||
bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||||
targets: '#bulkRenameFilesTableDiv tr',
|
targets: "#bulkRenameFilesTableDiv tr",
|
||||||
menu: 'multiRenameFilesMenu',
|
menu: "multiRenameFilesMenu",
|
||||||
actions: {
|
actions: {
|
||||||
ToggleSelection: function(element, ref) {
|
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 row = bulkRenameFilesTable.getNode(rowId);
|
||||||
const checkState = (row.checked === 1) ? 0 : 1;
|
const checkState = (row.checked === 1) ? 0 : 1;
|
||||||
bulkRenameFilesTable.toggleNodeTreeCheckbox(rowId, checkState);
|
bulkRenameFilesTable.toggleNodeTreeCheckbox(rowId, checkState);
|
||||||
|
@ -44,20 +43,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the dynamic table for bulk renaming
|
// Setup the dynamic table for bulk renaming
|
||||||
var bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
const bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
||||||
bulkRenameFilesTable.setup('bulkRenameFilesTableDiv', 'bulkRenameFilesTableFixedHeaderDiv', bulkRenameFilesContextMenu);
|
bulkRenameFilesTable.setup("bulkRenameFilesTableDiv", "bulkRenameFilesTableFixedHeaderDiv", bulkRenameFilesContextMenu);
|
||||||
|
|
||||||
// Inject checkbox into the first column of the table header
|
// Inject checkbox into the first column of the table header
|
||||||
var tableHeaders = $$('#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th');
|
const tableHeaders = $$("#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||||
var checkboxHeader;
|
let checkboxHeader;
|
||||||
if (tableHeaders.length > 0) {
|
if (tableHeaders.length > 0) {
|
||||||
if (checkboxHeader) {
|
if (checkboxHeader)
|
||||||
checkboxHeader.remove();
|
checkboxHeader.remove();
|
||||||
}
|
checkboxHeader = new Element("input");
|
||||||
checkboxHeader = new Element('input');
|
checkboxHeader.set("type", "checkbox");
|
||||||
checkboxHeader.set('type', 'checkbox');
|
checkboxHeader.set("id", "rootMultiRename_cb");
|
||||||
checkboxHeader.set('id', 'rootMultiRename_cb');
|
checkboxHeader.addEvent("click", (e) => {
|
||||||
checkboxHeader.addEvent('click', function(e) {
|
|
||||||
bulkRenameFilesTable.toggleGlobalCheckbox();
|
bulkRenameFilesTable.toggleGlobalCheckbox();
|
||||||
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
|
@ -69,16 +67,16 @@
|
||||||
|
|
||||||
// Register keyboard events to modal window
|
// Register keyboard events to modal window
|
||||||
// https://github.com/qbittorrent/qBittorrent/pull/18687#discussion_r1135045726
|
// https://github.com/qbittorrent/qBittorrent/pull/18687#discussion_r1135045726
|
||||||
var keyboard;
|
let keyboard;
|
||||||
if (!keyboard) {
|
if (!keyboard) {
|
||||||
keyboard = new Keyboard({
|
keyboard = new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Escape': function(event) {
|
"Escape": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': function(event) {
|
"Esc": function(event) {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -87,55 +85,55 @@
|
||||||
keyboard.activate();
|
keyboard.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileRenamer = new window.qBittorrent.MultiRename.RenameFiles();
|
const fileRenamer = new window.qBittorrent.MultiRename.RenameFiles();
|
||||||
fileRenamer.hash = data.hash;
|
fileRenamer.hash = data.hash;
|
||||||
|
|
||||||
// Load Multi Rename Preferences
|
// Load Multi Rename Preferences
|
||||||
var multiRenamePrefChecked = LocalPreferences.get('multirename_rememberPreferences', "true") === "true";
|
const multiRenamePrefChecked = LocalPreferences.get("multirename_rememberPreferences", "true") === "true";
|
||||||
$('multirename_rememberprefs_checkbox').setProperty('checked', multiRenamePrefChecked);
|
$("multirename_rememberprefs_checkbox").setProperty("checked", multiRenamePrefChecked);
|
||||||
|
|
||||||
if (multiRenamePrefChecked) {
|
if (multiRenamePrefChecked) {
|
||||||
var multirename_search = LocalPreferences.get('multirename_search', '');
|
const multirename_search = LocalPreferences.get("multirename_search", "");
|
||||||
fileRenamer.setSearch(multirename_search);
|
fileRenamer.setSearch(multirename_search);
|
||||||
$('multiRenameSearch').set('value', multirename_search);
|
$("multiRenameSearch").set("value", multirename_search);
|
||||||
|
|
||||||
var multirename_useRegex = LocalPreferences.get('multirename_useRegex', false);
|
const multirename_useRegex = LocalPreferences.get("multirename_useRegex", false);
|
||||||
fileRenamer.useRegex = multirename_useRegex === 'true';
|
fileRenamer.useRegex = multirename_useRegex === "true";
|
||||||
$('use_regex_search').checked = fileRenamer.useRegex;
|
$("use_regex_search").checked = fileRenamer.useRegex;
|
||||||
|
|
||||||
var multirename_matchAllOccurrences = LocalPreferences.get('multirename_matchAllOccurrences', false);
|
const multirename_matchAllOccurrences = LocalPreferences.get("multirename_matchAllOccurrences", false);
|
||||||
fileRenamer.matchAllOccurrences = multirename_matchAllOccurrences === 'true';
|
fileRenamer.matchAllOccurrences = multirename_matchAllOccurrences === "true";
|
||||||
$('match_all_occurrences').checked = fileRenamer.matchAllOccurrences;
|
$("match_all_occurrences").checked = fileRenamer.matchAllOccurrences;
|
||||||
|
|
||||||
var multirename_caseSensitive = LocalPreferences.get('multirename_caseSensitive', false);
|
const multirename_caseSensitive = LocalPreferences.get("multirename_caseSensitive", false);
|
||||||
fileRenamer.caseSensitive = multirename_caseSensitive === 'true';
|
fileRenamer.caseSensitive = multirename_caseSensitive === "true";
|
||||||
$('case_sensitive').checked = fileRenamer.caseSensitive;
|
$("case_sensitive").checked = fileRenamer.caseSensitive;
|
||||||
|
|
||||||
var multirename_replace = LocalPreferences.get('multirename_replace', '');
|
const multirename_replace = LocalPreferences.get("multirename_replace", "");
|
||||||
fileRenamer.setReplacement(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];
|
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);
|
const multirename_includeFiles = LocalPreferences.get("multirename_includeFiles", true);
|
||||||
fileRenamer.includeFiles = multirename_includeFiles === 'true';
|
fileRenamer.includeFiles = multirename_includeFiles === "true";
|
||||||
$('include_files').checked = fileRenamer.includeFiles;
|
$("include_files").checked = fileRenamer.includeFiles;
|
||||||
|
|
||||||
var multirename_includeFolders = LocalPreferences.get('multirename_includeFolders', false);
|
const multirename_includeFolders = LocalPreferences.get("multirename_includeFolders", false);
|
||||||
fileRenamer.includeFolders = multirename_includeFolders === 'true';
|
fileRenamer.includeFolders = multirename_includeFolders === "true";
|
||||||
$('include_folders').checked = fileRenamer.includeFolders;
|
$("include_folders").checked = fileRenamer.includeFolders;
|
||||||
|
|
||||||
var multirename_fileEnumerationStart = LocalPreferences.get('multirename_fileEnumerationStart', 0);
|
const multirename_fileEnumerationStart = LocalPreferences.get("multirename_fileEnumerationStart", 0);
|
||||||
fileRenamer.fileEnumerationStart = parseInt(multirename_fileEnumerationStart);
|
fileRenamer.fileEnumerationStart = parseInt(multirename_fileEnumerationStart, 10);
|
||||||
$('file_counter').set('value', fileRenamer.fileEnumerationStart);
|
$("file_counter").set("value", fileRenamer.fileEnumerationStart);
|
||||||
|
|
||||||
var multirename_replaceAll = LocalPreferences.get('multirename_replaceAll', false);
|
const multirename_replaceAll = LocalPreferences.get("multirename_replaceAll", false);
|
||||||
fileRenamer.replaceAll = multirename_replaceAll === 'true';
|
fileRenamer.replaceAll = multirename_replaceAll === "true";
|
||||||
var renameButtonValue = fileRenamer.replaceAll ? 'Replace All' : 'Replace';
|
const renameButtonValue = fileRenamer.replaceAll ? "Replace All" : "Replace";
|
||||||
$('renameOptions').set('value', renameButtonValue);
|
$("renameOptions").set("value", renameButtonValue);
|
||||||
$('renameButton').set('value', renameButtonValue);
|
$("renameButton").set("value", renameButtonValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fires every time a row's selection changes
|
// Fires every time a row's selection changes
|
||||||
|
@ -145,28 +143,28 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup Search Events that control renaming
|
// Setup Search Events that control renaming
|
||||||
$('multiRenameSearch').addEvent('input', function(e) {
|
$("multiRenameSearch").addEvent("input", (e) => {
|
||||||
let sanitized = e.target.value.replace(/\n/g, '');
|
const sanitized = e.target.value.replace(/\n/g, "");
|
||||||
$('multiRenameSearch').set('value', sanitized);
|
$("multiRenameSearch").set("value", sanitized);
|
||||||
|
|
||||||
// Search input has changed
|
// Search input has changed
|
||||||
$('multiRenameSearch').style['border-color'] = '';
|
$("multiRenameSearch").style["border-color"] = "";
|
||||||
LocalPreferences.set('multirename_search', sanitized);
|
LocalPreferences.set("multirename_search", sanitized);
|
||||||
fileRenamer.setSearch(sanitized);
|
fileRenamer.setSearch(sanitized);
|
||||||
});
|
});
|
||||||
$('use_regex_search').addEvent('change', function(e) {
|
$("use_regex_search").addEvent("change", (e) => {
|
||||||
fileRenamer.useRegex = e.target.checked;
|
fileRenamer.useRegex = e.target.checked;
|
||||||
LocalPreferences.set('multirename_useRegex', e.target.checked);
|
LocalPreferences.set("multirename_useRegex", e.target.checked);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
$('match_all_occurrences').addEvent('change', function(e) {
|
$("match_all_occurrences").addEvent("change", (e) => {
|
||||||
fileRenamer.matchAllOccurrences = e.target.checked;
|
fileRenamer.matchAllOccurrences = e.target.checked;
|
||||||
LocalPreferences.set('multirename_matchAllOccurrences', e.target.checked);
|
LocalPreferences.set("multirename_matchAllOccurrences", e.target.checked);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
$('case_sensitive').addEvent('change', function(e) {
|
$("case_sensitive").addEvent("change", (e) => {
|
||||||
fileRenamer.caseSensitive = e.target.checked;
|
fileRenamer.caseSensitive = e.target.checked;
|
||||||
LocalPreferences.set('multirename_caseSensitive', e.target.checked);
|
LocalPreferences.set("multirename_caseSensitive", e.target.checked);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -177,138 +175,136 @@
|
||||||
// Clear renamed column
|
// Clear renamed column
|
||||||
document
|
document
|
||||||
.querySelectorAll("span[id^='filesTablefileRenamed']")
|
.querySelectorAll("span[id^='filesTablefileRenamed']")
|
||||||
.forEach(function(span) {
|
.forEach((span) => {
|
||||||
span.set('text', "");
|
span.set("text", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update renamed column for matched rows
|
// Update renamed column for matched rows
|
||||||
for (let i = 0; i < matchedRows.length; ++i) {
|
for (let i = 0; i < matchedRows.length; ++i) {
|
||||||
const row = matchedRows[i];
|
const row = matchedRows[i];
|
||||||
$('filesTablefileRenamed' + row.rowId).set('text', row.renamed);
|
$("filesTablefileRenamed" + row.rowId).set("text", row.renamed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileRenamer.onInvalidRegex = function(err) {
|
fileRenamer.onInvalidRegex = function(err) {
|
||||||
$('multiRenameSearch').style['border-color'] = '#CC0033';
|
$("multiRenameSearch").style["border-color"] = "#CC0033";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup Replace Events that control renaming
|
// Setup Replace Events that control renaming
|
||||||
$('multiRenameReplace').addEvent('input', function(e) {
|
$("multiRenameReplace").addEvent("input", (e) => {
|
||||||
let sanitized = e.target.value.replace(/\n/g, '');
|
const sanitized = e.target.value.replace(/\n/g, "");
|
||||||
$('multiRenameReplace').set('value', sanitized);
|
$("multiRenameReplace").set("value", sanitized);
|
||||||
|
|
||||||
// Replace input has changed
|
// Replace input has changed
|
||||||
$('multiRenameReplace').style['border-color'] = '';
|
$("multiRenameReplace").style["border-color"] = "";
|
||||||
LocalPreferences.set('multirename_replace', sanitized);
|
LocalPreferences.set("multirename_replace", sanitized);
|
||||||
fileRenamer.setReplacement(sanitized);
|
fileRenamer.setReplacement(sanitized);
|
||||||
});
|
});
|
||||||
$('applies_to_option').addEvent('change', function(e) {
|
$("applies_to_option").addEvent("change", (e) => {
|
||||||
fileRenamer.appliesTo = e.target.value;
|
fileRenamer.appliesTo = e.target.value;
|
||||||
LocalPreferences.set('multirename_appliesTo', e.target.value);
|
LocalPreferences.set("multirename_appliesTo", e.target.value);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
$('include_files').addEvent('change', function(e) {
|
$("include_files").addEvent("change", (e) => {
|
||||||
fileRenamer.includeFiles = e.target.checked;
|
fileRenamer.includeFiles = e.target.checked;
|
||||||
LocalPreferences.set('multirename_includeFiles', e.target.checked);
|
LocalPreferences.set("multirename_includeFiles", e.target.checked);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
$('include_folders').addEvent('change', function(e) {
|
$("include_folders").addEvent("change", (e) => {
|
||||||
fileRenamer.includeFolders = e.target.checked;
|
fileRenamer.includeFolders = e.target.checked;
|
||||||
LocalPreferences.set('multirename_includeFolders', e.target.checked);
|
LocalPreferences.set("multirename_includeFolders", e.target.checked);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
$('file_counter').addEvent('input', function(e) {
|
$("file_counter").addEvent("input", (e) => {
|
||||||
let value = e.target.valueAsNumber;
|
let value = e.target.valueAsNumber;
|
||||||
if (!value) { value = 0; }
|
if (!value)
|
||||||
if (value < 0) { value = 0; }
|
value = 0;
|
||||||
if (value > 99999999) { value = 99999999; }
|
if (value < 0)
|
||||||
|
value = 0;
|
||||||
|
if (value > 99999999)
|
||||||
|
value = 99999999;
|
||||||
fileRenamer.fileEnumerationStart = value;
|
fileRenamer.fileEnumerationStart = value;
|
||||||
$('file_counter').set('value', value);
|
$("file_counter").set("value", value);
|
||||||
LocalPreferences.set('multirename_fileEnumerationStart', value);
|
LocalPreferences.set("multirename_fileEnumerationStart", value);
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup Rename Operation Events
|
// Setup Rename Operation Events
|
||||||
$('renameButton').addEvent('click', function(e) {
|
$("renameButton").addEvent("click", (e) => {
|
||||||
// Disable Search Options
|
// Disable Search Options
|
||||||
$('multiRenameSearch').disabled = true;
|
$("multiRenameSearch").disabled = true;
|
||||||
$('use_regex_search').disabled = true;
|
$("use_regex_search").disabled = true;
|
||||||
$('match_all_occurrences').disabled = true;
|
$("match_all_occurrences").disabled = true;
|
||||||
$('case_sensitive').disabled = true;
|
$("case_sensitive").disabled = true;
|
||||||
// Disable Replace Options
|
// Disable Replace Options
|
||||||
$('multiRenameReplace').disabled = true;
|
$("multiRenameReplace").disabled = true;
|
||||||
$('applies_to_option').disabled = true;
|
$("applies_to_option").disabled = true;
|
||||||
$('include_files').disabled = true;
|
$("include_files").disabled = true;
|
||||||
$('include_folders').disabled = true;
|
$("include_folders").disabled = true;
|
||||||
$('file_counter').disabled = true;
|
$("file_counter").disabled = true;
|
||||||
// Disable Rename Buttons
|
// Disable Rename Buttons
|
||||||
$('renameButton').disabled = true;
|
$("renameButton").disabled = true;
|
||||||
$('renameOptions').disabled = true;
|
$("renameOptions").disabled = true;
|
||||||
// Clear error text
|
// Clear error text
|
||||||
$('rename_error').set('text', '');
|
$("rename_error").set("text", "");
|
||||||
fileRenamer.rename();
|
fileRenamer.rename();
|
||||||
});
|
});
|
||||||
fileRenamer.onRenamed = function(rows) {
|
fileRenamer.onRenamed = function(rows) {
|
||||||
// Disable Search Options
|
// Disable Search Options
|
||||||
$('multiRenameSearch').disabled = false;
|
$("multiRenameSearch").disabled = false;
|
||||||
$('use_regex_search').disabled = false;
|
$("use_regex_search").disabled = false;
|
||||||
$('match_all_occurrences').disabled = false;
|
$("match_all_occurrences").disabled = false;
|
||||||
$('case_sensitive').disabled = false;
|
$("case_sensitive").disabled = false;
|
||||||
// Disable Replace Options
|
// Disable Replace Options
|
||||||
$('multiRenameReplace').disabled = false;
|
$("multiRenameReplace").disabled = false;
|
||||||
$('applies_to_option').disabled = false;
|
$("applies_to_option").disabled = false;
|
||||||
$('include_files').disabled = false;
|
$("include_files").disabled = false;
|
||||||
$('include_folders').disabled = false;
|
$("include_folders").disabled = false;
|
||||||
$('file_counter').disabled = false;
|
$("file_counter").disabled = false;
|
||||||
// Disable Rename Buttons
|
// Disable Rename Buttons
|
||||||
$('renameButton').disabled = false;
|
$("renameButton").disabled = false;
|
||||||
$('renameOptions').disabled = false;
|
$("renameOptions").disabled = false;
|
||||||
|
|
||||||
// Recreate table
|
// Recreate table
|
||||||
let selectedRows = bulkRenameFilesTable.getSelectedRows().map(row => row.rowId.toString());
|
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());
|
selectedRows = selectedRows.filter(selectedRow => selectedRow !== renamedRow.rowId.toString());
|
||||||
}
|
|
||||||
bulkRenameFilesTable.clear();
|
bulkRenameFilesTable.clear();
|
||||||
|
|
||||||
// Adjust file enumeration count by 1 when replacing single files to prevent naming conflicts
|
// Adjust file enumeration count by 1 when replacing single files to prevent naming conflicts
|
||||||
if (!fileRenamer.replaceAll) {
|
if (!fileRenamer.replaceAll) {
|
||||||
fileRenamer.fileEnumerationStart++;
|
fileRenamer.fileEnumerationStart++;
|
||||||
$('file_counter').set('value', fileRenamer.fileEnumerationStart);
|
$("file_counter").set("value", fileRenamer.fileEnumerationStart);
|
||||||
}
|
}
|
||||||
setupTable(selectedRows);
|
setupTable(selectedRows);
|
||||||
};
|
};
|
||||||
fileRenamer.onRenameError = function(err, row) {
|
fileRenamer.onRenameError = function(err, row) {
|
||||||
if (err.xhr.status === 409) {
|
if (err.xhr.status === 409)
|
||||||
$('rename_error').set('text', `QBT_TR(Rename failed: file or folder already exists)QBT_TR[CONTEXT=PropertiesWidget] \`${row.renamed}\``);
|
$("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 combobox = e.target;
|
||||||
const replaceOperation = combobox.value;
|
const replaceOperation = combobox.value;
|
||||||
if (replaceOperation === "Replace") {
|
if (replaceOperation === "Replace")
|
||||||
fileRenamer.replaceAll = false;
|
fileRenamer.replaceAll = false;
|
||||||
}
|
else if (replaceOperation === "Replace All")
|
||||||
else if (replaceOperation === "Replace All") {
|
|
||||||
fileRenamer.replaceAll = true;
|
fileRenamer.replaceAll = true;
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
fileRenamer.replaceAll = false;
|
fileRenamer.replaceAll = false;
|
||||||
}
|
LocalPreferences.set("multirename_replaceAll", fileRenamer.replaceAll);
|
||||||
LocalPreferences.set('multirename_replaceAll', fileRenamer.replaceAll);
|
$("renameButton").set("value", replaceOperation);
|
||||||
$('renameButton').set('value', replaceOperation);
|
|
||||||
});
|
});
|
||||||
$('closeButton').addEvent('click', function() {
|
$("closeButton").addEvent("click", () => {
|
||||||
window.parent.qBittorrent.Client.closeWindows();
|
window.parent.qBittorrent.Client.closeWindows();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
// synchronize header scrolling to table body
|
// synchronize header scrolling to table body
|
||||||
$('bulkRenameFilesTableDiv').onscroll = function() {
|
$("bulkRenameFilesTableDiv").onscroll = function() {
|
||||||
const length = $(this).scrollLeft;
|
const length = $(this).scrollLeft;
|
||||||
$('bulkRenameFilesTableFixedHeaderDiv').scrollLeft = length;
|
$("bulkRenameFilesTableFixedHeaderDiv").scrollLeft = length;
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleTorrentFiles = function(files, selectedRows) {
|
const handleTorrentFiles = function(files, selectedRows) {
|
||||||
const rows = files.map(function(file, index) {
|
const rows = files.map((file, index) => {
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
fileId: index,
|
fileId: index,
|
||||||
|
@ -325,20 +321,19 @@
|
||||||
addRowsToTable(rows, selectedRows);
|
addRowsToTable(rows, selectedRows);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addRowsToTable = function(rows, selectedRows) {
|
const addRowsToTable = function(rows, selectedRows) {
|
||||||
let rowId = 0;
|
let rowId = 0;
|
||||||
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
||||||
rootNode.autoCheckFolders = false;
|
rootNode.autoCheckFolders = false;
|
||||||
|
|
||||||
rows.forEach(function(row) {
|
rows.forEach((row) => {
|
||||||
const pathItems = row.path.split(window.qBittorrent.Filesystem.PathSeparator);
|
const pathItems = row.path.split(window.qBittorrent.Filesystem.PathSeparator);
|
||||||
|
|
||||||
pathItems.pop(); // remove last item (i.e. file name)
|
pathItems.pop(); // remove last item (i.e. file name)
|
||||||
let parent = rootNode;
|
let parent = rootNode;
|
||||||
pathItems.forEach(function(folderName) {
|
pathItems.forEach((folderName) => {
|
||||||
if (folderName === '.unwanted') {
|
if (folderName === ".unwanted")
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
let folderNode = null;
|
let folderNode = null;
|
||||||
if (parent.children !== null) {
|
if (parent.children !== null) {
|
||||||
|
@ -387,25 +382,23 @@
|
||||||
bulkRenameFilesTable.updateTable(false);
|
bulkRenameFilesTable.updateTable(false);
|
||||||
bulkRenameFilesTable.altRow();
|
bulkRenameFilesTable.altRow();
|
||||||
|
|
||||||
if (selectedRows !== undefined) {
|
if (selectedRows !== undefined)
|
||||||
bulkRenameFilesTable.reselectRows(selectedRows);
|
bulkRenameFilesTable.reselectRows(selectedRows);
|
||||||
}
|
|
||||||
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
fileRenamer.selectedFiles = bulkRenameFilesTable.getSelectedRows();
|
||||||
fileRenamer.update();
|
fileRenamer.update();
|
||||||
};
|
};
|
||||||
|
|
||||||
var setupTable = function(selectedRows) {
|
const setupTable = function(selectedRows) {
|
||||||
new Request.JSON({
|
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,
|
noCache: true,
|
||||||
method: 'get',
|
method: "get",
|
||||||
onSuccess: function(files) {
|
onSuccess: function(files) {
|
||||||
if (files.length === 0) {
|
if (files.length === 0)
|
||||||
bulkRenameFilesTable.clear();
|
bulkRenameFilesTable.clear();
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
handleTorrentFiles(files, selectedRows);
|
handleTorrentFiles(files, selectedRows);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,57 +9,57 @@
|
||||||
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
<script src="scripts/lib/MooTools-More-1.6.0-compat-compressed.js"></script>
|
||||||
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
<script src="scripts/misc.js?locale=${LANG}&v=${CACHEID}"></script>
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
new Keyboard({
|
new Keyboard({
|
||||||
defaultEventType: 'keydown',
|
defaultEventType: "keydown",
|
||||||
events: {
|
events: {
|
||||||
'Enter': (event) => {
|
"Enter": (event) => {
|
||||||
$('renameButton').click();
|
$("renameButton").click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Escape': (event) => {
|
"Escape": (event) => {
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
'Esc': (event) => {
|
"Esc": (event) => {
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).activate();
|
}).activate();
|
||||||
window.addEvent('domready', () => {
|
window.addEvent("domready", () => {
|
||||||
const oldName = new URI().getData('rule');
|
const oldName = new URI().getData("rule");
|
||||||
|
|
||||||
$('rename').value = oldName;
|
$("rename").value = oldName;
|
||||||
$('rename').focus();
|
$("rename").focus();
|
||||||
$('rename').setSelectionRange(0, oldName.length);
|
$("rename").setSelectionRange(0, oldName.length);
|
||||||
|
|
||||||
$('renameButton').addEvent('click', (e) => {
|
$("renameButton").addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
// check field
|
// check field
|
||||||
const newName = $('rename').value.trim();
|
const newName = $("rename").value.trim();
|
||||||
if (newName === '') {
|
if (newName === "") {
|
||||||
alert('QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name cannot be empty)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newName === oldName) {
|
if (newName === oldName) {
|
||||||
alert('QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]');
|
alert("QBT_TR(Name is unchanged)QBT_TR[CONTEXT=HttpServer]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('renameButton').disabled = true;
|
$("renameButton").disabled = true;
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/rss/renameRule',
|
url: "api/v2/rss/renameRule",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
ruleName: oldName,
|
ruleName: oldName,
|
||||||
newRuleName: newName
|
newRuleName: newName
|
||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
window.parent.qBittorrent.RssDownloader.updateRulesList();
|
||||||
window.parent.MochaUI.closeWindow(window.parent.$('renameRulePage'));
|
window.parent.MochaUI.closeWindow(window.parent.$("renameRulePage"));
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined)
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
|
@ -46,7 +46,7 @@ window.qBittorrent.Cache = (() => {
|
||||||
const keys = Reflect.ownKeys(obj);
|
const keys = Reflect.ownKeys(obj);
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = obj[key];
|
const value = obj[key];
|
||||||
if ((value && (typeof value === 'object')) || (typeof value === 'function'))
|
if ((value && (typeof value === "object")) || (typeof value === "function"))
|
||||||
deepFreeze(value);
|
deepFreeze(value);
|
||||||
}
|
}
|
||||||
Object.freeze(obj);
|
Object.freeze(obj);
|
||||||
|
@ -57,8 +57,8 @@ window.qBittorrent.Cache = (() => {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
new Request.JSON({
|
new Request.JSON({
|
||||||
url: 'api/v2/app/buildInfo',
|
url: "api/v2/app/buildInfo",
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onSuccess: (responseJSON) => {
|
onSuccess: (responseJSON) => {
|
||||||
if (!responseJSON)
|
if (!responseJSON)
|
||||||
|
@ -84,11 +84,11 @@ window.qBittorrent.Cache = (() => {
|
||||||
// }
|
// }
|
||||||
init(obj = {}) {
|
init(obj = {}) {
|
||||||
new Request.JSON({
|
new Request.JSON({
|
||||||
url: 'api/v2/app/preferences',
|
url: "api/v2/app/preferences",
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onFailure: (xhr) => {
|
onFailure: (xhr) => {
|
||||||
if (typeof obj.onFailure === 'function')
|
if (typeof obj.onFailure === "function")
|
||||||
obj.onFailure(xhr);
|
obj.onFailure(xhr);
|
||||||
},
|
},
|
||||||
onSuccess: (responseJSON, responseText) => {
|
onSuccess: (responseJSON, responseText) => {
|
||||||
|
@ -98,7 +98,7 @@ window.qBittorrent.Cache = (() => {
|
||||||
deepFreeze(responseJSON);
|
deepFreeze(responseJSON);
|
||||||
this.#m_store = responseJSON;
|
this.#m_store = responseJSON;
|
||||||
|
|
||||||
if (typeof obj.onSuccess === 'function')
|
if (typeof obj.onSuccess === "function")
|
||||||
obj.onSuccess(responseJSON, responseText);
|
obj.onSuccess(responseJSON, responseText);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -114,19 +114,19 @@ window.qBittorrent.Cache = (() => {
|
||||||
// onSuccess: () => {}
|
// onSuccess: () => {}
|
||||||
// }
|
// }
|
||||||
set(obj) {
|
set(obj) {
|
||||||
if (typeof obj !== 'object')
|
if (typeof obj !== "object")
|
||||||
throw new Error('`obj` is not an object.');
|
throw new Error("`obj` is not an object.");
|
||||||
if (typeof obj.data !== 'object')
|
if (typeof obj.data !== "object")
|
||||||
throw new Error('`data` is not an object.');
|
throw new Error("`data` is not an object.");
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/app/setPreferences',
|
url: "api/v2/app/setPreferences",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
'json': JSON.stringify(obj.data)
|
"json": JSON.stringify(obj.data)
|
||||||
},
|
},
|
||||||
onFailure: (xhr) => {
|
onFailure: (xhr) => {
|
||||||
if (typeof obj.onFailure === 'function')
|
if (typeof obj.onFailure === "function")
|
||||||
obj.onFailure(xhr);
|
obj.onFailure(xhr);
|
||||||
},
|
},
|
||||||
onSuccess: (responseText, responseXML) => {
|
onSuccess: (responseText, responseXML) => {
|
||||||
|
@ -140,7 +140,7 @@ window.qBittorrent.Cache = (() => {
|
||||||
}
|
}
|
||||||
deepFreeze(this.#m_store);
|
deepFreeze(this.#m_store);
|
||||||
|
|
||||||
if (typeof obj.onSuccess === 'function')
|
if (typeof obj.onSuccess === "function")
|
||||||
obj.onSuccess(responseText, responseXML);
|
obj.onSuccess(responseText, responseXML);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -148,12 +148,12 @@ window.qBittorrent.Cache = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QbtVersionCache {
|
class QbtVersionCache {
|
||||||
#m_store = '';
|
#m_store = "";
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/app/version',
|
url: "api/v2/app/version",
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onSuccess: (responseText) => {
|
onSuccess: (responseText) => {
|
||||||
if (!responseText)
|
if (!responseText)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.ContextMenu = (function() {
|
window.qBittorrent.ContextMenu = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -48,15 +47,15 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
|
|
||||||
let lastShownContextMenu = null;
|
let lastShownContextMenu = null;
|
||||||
const ContextMenu = new Class({
|
const ContextMenu = new Class({
|
||||||
//implements
|
// implements
|
||||||
Implements: [Options, Events],
|
Implements: [Options, Events],
|
||||||
|
|
||||||
//options
|
// options
|
||||||
options: {
|
options: {
|
||||||
actions: {},
|
actions: {},
|
||||||
menu: 'menu_id',
|
menu: "menu_id",
|
||||||
stopEvent: true,
|
stopEvent: true,
|
||||||
targets: 'body',
|
targets: "body",
|
||||||
offsets: {
|
offsets: {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
|
@ -68,37 +67,35 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
touchTimer: 600
|
touchTimer: 600
|
||||||
},
|
},
|
||||||
|
|
||||||
//initialization
|
// initialization
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
//set options
|
// set options
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
|
|
||||||
//option diffs menu
|
// option diffs menu
|
||||||
this.menu = $(this.options.menu);
|
this.menu = $(this.options.menu);
|
||||||
this.targets = $$(this.options.targets);
|
this.targets = $$(this.options.targets);
|
||||||
|
|
||||||
//fx
|
// fx
|
||||||
this.fx = new Fx.Tween(this.menu, {
|
this.fx = new Fx.Tween(this.menu, {
|
||||||
property: 'opacity',
|
property: "opacity",
|
||||||
duration: this.options.fadeSpeed,
|
duration: this.options.fadeSpeed,
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
if (this.getStyle('opacity')) {
|
if (this.getStyle("opacity"))
|
||||||
this.setStyle('visibility', 'visible');
|
this.setStyle("visibility", "visible");
|
||||||
}
|
else
|
||||||
else {
|
this.setStyle("visibility", "hidden");
|
||||||
this.setStyle('visibility', 'hidden');
|
|
||||||
}
|
|
||||||
}.bind(this.menu)
|
}.bind(this.menu)
|
||||||
});
|
});
|
||||||
|
|
||||||
//hide and begin the listener
|
// hide and begin the listener
|
||||||
this.hide().startListener();
|
this.hide().startListener();
|
||||||
|
|
||||||
//hide the menu
|
// hide the menu
|
||||||
this.menu.setStyles({
|
this.menu.setStyles({
|
||||||
'position': 'absolute',
|
"position": "absolute",
|
||||||
'top': '-900000px',
|
"top": "-900000px",
|
||||||
'display': 'block'
|
"display": "block"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -107,13 +104,13 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
|
|
||||||
const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75;
|
const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75;
|
||||||
|
|
||||||
if (this.menu.hasClass('scrollableMenu'))
|
if (this.menu.hasClass("scrollableMenu"))
|
||||||
this.menu.setStyle('max-height', scrollableMenuMaxHeight);
|
this.menu.setStyle("max-height", scrollableMenuMaxHeight);
|
||||||
|
|
||||||
// draw the menu off-screen to know the menu dimensions
|
// draw the menu off-screen to know the menu dimensions
|
||||||
this.menu.setStyles({
|
this.menu.setStyles({
|
||||||
left: '-999em',
|
left: "-999em",
|
||||||
top: '-999em'
|
top: "-999em"
|
||||||
});
|
});
|
||||||
|
|
||||||
// position the menu
|
// position the menu
|
||||||
|
@ -130,16 +127,16 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
this.menu.setStyles({
|
this.menu.setStyles({
|
||||||
left: xPosMenu,
|
left: xPosMenu,
|
||||||
top: yPosMenu,
|
top: yPosMenu,
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
'z-index': '2000'
|
"z-index": "2000"
|
||||||
});
|
});
|
||||||
|
|
||||||
// position the sub-menu
|
// position the sub-menu
|
||||||
const uls = this.menu.getElementsByTagName('ul');
|
const uls = this.menu.getElementsByTagName("ul");
|
||||||
for (let i = 0; i < uls.length; ++i) {
|
for (let i = 0; i < uls.length; ++i) {
|
||||||
const ul = uls[i];
|
const ul = uls[i];
|
||||||
if (ul.hasClass('scrollableMenu'))
|
if (ul.hasClass("scrollableMenu"))
|
||||||
ul.setStyle('max-height', scrollableMenuMaxHeight);
|
ul.setStyle("max-height", scrollableMenuMaxHeight);
|
||||||
const rectParent = ul.parentNode.getBoundingClientRect();
|
const rectParent = ul.parentNode.getBoundingClientRect();
|
||||||
const xPosOrigin = rectParent.left;
|
const xPosOrigin = rectParent.left;
|
||||||
const yPosOrigin = rectParent.bottom;
|
const yPosOrigin = rectParent.bottom;
|
||||||
|
@ -154,26 +151,26 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
if (yPos < 0)
|
if (yPos < 0)
|
||||||
yPos = 0;
|
yPos = 0;
|
||||||
ul.setStyles({
|
ul.setStyles({
|
||||||
'margin-left': xPos - xPosOrigin,
|
"margin-left": xPos - xPosOrigin,
|
||||||
'margin-top': yPos - yPosOrigin
|
"margin-top": yPos - yPosOrigin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setupEventListeners: function(elem) {
|
setupEventListeners: function(elem) {
|
||||||
elem.addEvent('contextmenu', function(e) {
|
elem.addEvent("contextmenu", (e) => {
|
||||||
this.triggerMenu(e, elem);
|
this.triggerMenu(e, elem);
|
||||||
}.bind(this));
|
});
|
||||||
elem.addEvent('click', function(e) {
|
elem.addEvent("click", (e) => {
|
||||||
this.hide();
|
this.hide();
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
elem.addEvent('touchstart', function(e) {
|
elem.addEvent("touchstart", (e) => {
|
||||||
this.hide();
|
this.hide();
|
||||||
this.touchStartAt = performance.now();
|
this.touchStartAt = performance.now();
|
||||||
this.touchStartEvent = e;
|
this.touchStartEvent = e;
|
||||||
}.bind(this));
|
});
|
||||||
elem.addEvent('touchend', function(e) {
|
elem.addEvent("touchend", (e) => {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const touchStartAt = this.touchStartAt;
|
const touchStartAt = this.touchStartAt;
|
||||||
const touchStartEvent = this.touchStartEvent;
|
const touchStartEvent = this.touchStartEvent;
|
||||||
|
@ -182,16 +179,15 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
this.touchStartEvent = null;
|
this.touchStartEvent = null;
|
||||||
|
|
||||||
const isTargetUnchanged = (Math.abs(e.event.pageX - touchStartEvent.event.pageX) <= 10) && (Math.abs(e.event.pageY - touchStartEvent.event.pageY) <= 10);
|
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);
|
this.triggerMenu(touchStartEvent, elem);
|
||||||
}
|
});
|
||||||
}.bind(this));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addTarget: function(t) {
|
addTarget: function(t) {
|
||||||
// prevent long press from selecting this text
|
// prevent long press from selecting this text
|
||||||
t.style.setProperty('user-select', 'none');
|
t.style.setProperty("user-select", "none");
|
||||||
t.style.setProperty('-webkit-user-select', 'none');
|
t.style.setProperty("-webkit-user-select", "none");
|
||||||
|
|
||||||
this.targets[this.targets.length] = t;
|
this.targets[this.targets.length] = t;
|
||||||
this.setupEventListeners(t);
|
this.setupEventListeners(t);
|
||||||
|
@ -201,102 +197,100 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
if (this.options.disabled)
|
if (this.options.disabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//prevent default, if told to
|
// prevent default, if told to
|
||||||
if (this.options.stopEvent) {
|
if (this.options.stopEvent)
|
||||||
e.stop();
|
e.stop();
|
||||||
}
|
// record this as the trigger
|
||||||
//record this as the trigger
|
|
||||||
this.options.element = $(el);
|
this.options.element = $(el);
|
||||||
this.adjustMenuPosition(e);
|
this.adjustMenuPosition(e);
|
||||||
//show the menu
|
// show the menu
|
||||||
this.show();
|
this.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
//get things started
|
// get things started
|
||||||
startListener: function() {
|
startListener: function() {
|
||||||
/* all elements */
|
/* all elements */
|
||||||
this.targets.each(function(el) {
|
this.targets.each((el) => {
|
||||||
this.setupEventListeners(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);
|
}, this);
|
||||||
|
|
||||||
//hide on body click
|
/* menu items */
|
||||||
$(document.body).addEvent('click', function() {
|
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();
|
this.hide();
|
||||||
}.bind(this));
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMenuItems: function() {},
|
updateMenuItems: function() {},
|
||||||
|
|
||||||
//show menu
|
// show menu
|
||||||
show: function(trigger) {
|
show: function(trigger) {
|
||||||
if (lastShownContextMenu && (lastShownContextMenu !== this))
|
if (lastShownContextMenu && (lastShownContextMenu !== this))
|
||||||
lastShownContextMenu.hide();
|
lastShownContextMenu.hide();
|
||||||
this.fx.start(1);
|
this.fx.start(1);
|
||||||
this.fireEvent('show');
|
this.fireEvent("show");
|
||||||
lastShownContextMenu = this;
|
lastShownContextMenu = this;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
//hide the menu
|
// hide the menu
|
||||||
hide: function(trigger) {
|
hide: function(trigger) {
|
||||||
if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== 'hidden')) {
|
if (lastShownContextMenu && (lastShownContextMenu.menu.style.visibility !== "hidden")) {
|
||||||
this.fx.start(0);
|
this.fx.start(0);
|
||||||
//this.menu.fade('out');
|
// this.menu.fade('out');
|
||||||
this.fireEvent('hide');
|
this.fireEvent("hide");
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
setItemChecked: function(item, checked) {
|
setItemChecked: function(item, checked) {
|
||||||
this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity =
|
this.menu.getElement("a[href$=" + item + "]").firstChild.style.opacity =
|
||||||
checked ? '1' : '0';
|
checked ? "1" : "0";
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
getItemChecked: function(item) {
|
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) {
|
hideItem: function(item) {
|
||||||
this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible');
|
this.menu.getElement("a[href$=" + item + "]").parentNode.addClass("invisible");
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
//show an item
|
// show an item
|
||||||
showItem: function(item) {
|
showItem: function(item) {
|
||||||
this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible');
|
this.menu.getElement("a[href$=" + item + "]").parentNode.removeClass("invisible");
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
//disable the entire menu
|
// disable the entire menu
|
||||||
disable: function() {
|
disable: function() {
|
||||||
this.options.disabled = true;
|
this.options.disabled = true;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
//enable the entire menu
|
// enable the entire menu
|
||||||
enable: function() {
|
enable: function() {
|
||||||
this.options.disabled = false;
|
this.options.disabled = false;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
//execute an action
|
// execute an action
|
||||||
execute: function(action, element) {
|
execute: function(action, element) {
|
||||||
if (this.options.actions[action]) {
|
if (this.options.actions[action])
|
||||||
this.options.actions[action](element, this, action);
|
this.options.actions[action](element, this, action);
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -323,37 +317,37 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
selectedRows.forEach((item, index) => {
|
selectedRows.forEach((item, index) => {
|
||||||
const data = torrentsTable.rows.get(item).full_data;
|
const data = torrentsTable.rows.get(item).full_data;
|
||||||
|
|
||||||
if (data['seq_dl'] !== true)
|
if (data["seq_dl"] !== true)
|
||||||
all_are_seq_dl = false;
|
all_are_seq_dl = false;
|
||||||
else
|
else
|
||||||
there_are_seq_dl = true;
|
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;
|
all_are_f_l_piece_prio = false;
|
||||||
else
|
else
|
||||||
there_are_f_l_piece_prio = true;
|
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;
|
all_are_downloaded = false;
|
||||||
else if (data['super_seeding'] !== true)
|
else if (data["super_seeding"] !== true)
|
||||||
all_are_super_seeding = false;
|
all_are_super_seeding = false;
|
||||||
|
|
||||||
if ((data['state'] !== 'stoppedUP') && (data['state'] !== 'stoppedDL'))
|
if ((data["state"] !== "stoppedUP") && (data["state"] !== "stoppedDL"))
|
||||||
all_are_stopped = false;
|
all_are_stopped = false;
|
||||||
else
|
else
|
||||||
there_are_stopped = true;
|
there_are_stopped = true;
|
||||||
|
|
||||||
if (data['force_start'] !== true)
|
if (data["force_start"] !== true)
|
||||||
all_are_force_start = false;
|
all_are_force_start = false;
|
||||||
else
|
else
|
||||||
there_are_force_start = true;
|
there_are_force_start = true;
|
||||||
|
|
||||||
if (data['auto_tmm'] === true)
|
if (data["auto_tmm"] === true)
|
||||||
there_are_auto_tmm = true;
|
there_are_auto_tmm = true;
|
||||||
else
|
else
|
||||||
all_are_auto_tmm = false;
|
all_are_auto_tmm = false;
|
||||||
|
|
||||||
const torrentTags = data['tags'].split(', ');
|
const torrentTags = data["tags"].split(", ");
|
||||||
for (const tag of torrentTags) {
|
for (const tag of torrentTags) {
|
||||||
const count = tagCount.get(tag);
|
const count = tagCount.get(tag);
|
||||||
tagCount.set(tag, ((count !== undefined) ? (count + 1) : 1));
|
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
|
// hide renameFiles when more than 1 torrent is selected
|
||||||
if (selectedRows.length === 1) {
|
if (selectedRows.length === 1) {
|
||||||
const data = torrentsTable.rows.get(selectedRows[0]).full_data;
|
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
|
// hide renameFiles when metadata hasn't been downloaded yet
|
||||||
metadata_downloaded
|
metadata_downloaded
|
||||||
? this.showItem('renameFiles')
|
? this.showItem("renameFiles")
|
||||||
: this.hideItem('renameFiles');
|
: this.hideItem("renameFiles");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.hideItem('renameFiles');
|
this.hideItem("renameFiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (all_are_downloaded) {
|
if (all_are_downloaded) {
|
||||||
this.hideItem('downloadLimit');
|
this.hideItem("downloadLimit");
|
||||||
this.menu.getElement('a[href$=uploadLimit]').parentNode.addClass('separator');
|
this.menu.getElement("a[href$=uploadLimit]").parentNode.addClass("separator");
|
||||||
this.hideItem('sequentialDownload');
|
this.hideItem("sequentialDownload");
|
||||||
this.hideItem('firstLastPiecePrio');
|
this.hideItem("firstLastPiecePrio");
|
||||||
this.showItem('superSeeding');
|
this.showItem("superSeeding");
|
||||||
this.setItemChecked('superSeeding', all_are_super_seeding);
|
this.setItemChecked("superSeeding", all_are_super_seeding);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const show_seq_dl = (all_are_seq_dl || !there_are_seq_dl);
|
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);
|
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)
|
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
|
else
|
||||||
this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.removeClass('separator');
|
this.menu.getElement("a[href$=firstLastPiecePrio]").parentNode.removeClass("separator");
|
||||||
|
|
||||||
if (show_seq_dl)
|
if (show_seq_dl)
|
||||||
this.showItem('sequentialDownload');
|
this.showItem("sequentialDownload");
|
||||||
else
|
else
|
||||||
this.hideItem('sequentialDownload');
|
this.hideItem("sequentialDownload");
|
||||||
|
|
||||||
if (show_f_l_piece_prio)
|
if (show_f_l_piece_prio)
|
||||||
this.showItem('firstLastPiecePrio');
|
this.showItem("firstLastPiecePrio");
|
||||||
else
|
else
|
||||||
this.hideItem('firstLastPiecePrio');
|
this.hideItem("firstLastPiecePrio");
|
||||||
|
|
||||||
this.setItemChecked('sequentialDownload', all_are_seq_dl);
|
this.setItemChecked("sequentialDownload", all_are_seq_dl);
|
||||||
this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio);
|
this.setItemChecked("firstLastPiecePrio", all_are_f_l_piece_prio);
|
||||||
|
|
||||||
this.showItem('downloadLimit');
|
this.showItem("downloadLimit");
|
||||||
this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator');
|
this.menu.getElement("a[href$=uploadLimit]").parentNode.removeClass("separator");
|
||||||
this.hideItem('superSeeding');
|
this.hideItem("superSeeding");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showItem('start');
|
this.showItem("start");
|
||||||
this.showItem('stop');
|
this.showItem("stop");
|
||||||
this.showItem('forceStart');
|
this.showItem("forceStart");
|
||||||
if (all_are_stopped)
|
if (all_are_stopped)
|
||||||
this.hideItem('stop');
|
this.hideItem("stop");
|
||||||
else if (all_are_force_start)
|
else if (all_are_force_start)
|
||||||
this.hideItem('forceStart');
|
this.hideItem("forceStart");
|
||||||
else if (!there_are_stopped && !there_are_force_start)
|
else if (!there_are_stopped && !there_are_force_start)
|
||||||
this.hideItem('start');
|
this.hideItem("start");
|
||||||
|
|
||||||
if (!all_are_auto_tmm && there_are_auto_tmm) {
|
if (!all_are_auto_tmm && there_are_auto_tmm) {
|
||||||
this.hideItem('autoTorrentManagement');
|
this.hideItem("autoTorrentManagement");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.showItem('autoTorrentManagement');
|
this.showItem("autoTorrentManagement");
|
||||||
this.setItemChecked('autoTorrentManagement', all_are_auto_tmm);
|
this.setItemChecked("autoTorrentManagement", all_are_auto_tmm);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextTagList = $('contextTagList');
|
const contextTagList = $("contextTagList");
|
||||||
tagList.forEach((tag, tagHash) => {
|
tagList.forEach((tag, tagHash) => {
|
||||||
const checkbox = contextTagList.getElement(`a[href="#Tag/${tagHash}"] input[type="checkbox"]`);
|
const checkbox = contextTagList.getElement(`a[href="#Tag/${tagHash}"] input[type="checkbox"]`);
|
||||||
const count = tagCount.get(tag.name);
|
const count = tagCount.get(tag.name);
|
||||||
|
@ -439,12 +433,12 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCategoriesSubMenu: function(categoryList) {
|
updateCategoriesSubMenu: function(categoryList) {
|
||||||
const contextCategoryList = $('contextCategoryList');
|
const contextCategoryList = $("contextCategoryList");
|
||||||
contextCategoryList.getChildren().each(c => c.destroy());
|
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>'
|
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>'
|
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;
|
let first = true;
|
||||||
for (const { categoryName, categoryHash } of sortedCategories) {
|
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>`
|
html: `<a href="javascript:torrentSetCategoryFN(${categoryHash});"><img src="images/view-categories.svg"/>${window.qBittorrent.Misc.escapeHtml(categoryName)}</a>`
|
||||||
});
|
});
|
||||||
if (first) {
|
if (first) {
|
||||||
el.addClass('separator');
|
el.addClass("separator");
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
contextCategoryList.appendChild(el);
|
contextCategoryList.appendChild(el);
|
||||||
|
@ -470,21 +464,21 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTagsSubMenu: function(tagList) {
|
updateTagsSubMenu: function(tagList) {
|
||||||
const contextTagList = $('contextTagList');
|
const contextTagList = $("contextTagList");
|
||||||
while (contextTagList.firstChild !== null)
|
while (contextTagList.firstChild !== null)
|
||||||
contextTagList.removeChild(contextTagList.firstChild);
|
contextTagList.removeChild(contextTagList.firstChild);
|
||||||
|
|
||||||
contextTagList.appendChild(new Element('li', {
|
contextTagList.appendChild(new Element("li", {
|
||||||
html: '<a href="javascript:torrentAddTagsFN();">'
|
html: '<a href="javascript:torrentAddTagsFN();">'
|
||||||
+ '<img src="images/list-add.svg" alt="QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
+ '<img src="images/list-add.svg" alt="QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
||||||
+ ' QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]'
|
+ " QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]"
|
||||||
+ '</a>'
|
+ "</a>"
|
||||||
}));
|
}));
|
||||||
contextTagList.appendChild(new Element('li', {
|
contextTagList.appendChild(new Element("li", {
|
||||||
html: '<a href="javascript:torrentRemoveAllTagsFN();">'
|
html: '<a href="javascript:torrentRemoveAllTagsFN();">'
|
||||||
+ '<img src="images/edit-clear.svg" alt="QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
+ '<img src="images/edit-clear.svg" alt="QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"/>'
|
||||||
+ ' QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]'
|
+ " QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]"
|
||||||
+ '</a>'
|
+ "</a>"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const sortedTags = [];
|
const sortedTags = [];
|
||||||
|
@ -496,13 +490,13 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
|
|
||||||
for (let i = 0; i < sortedTags.length; ++i) {
|
for (let i = 0; i < sortedTags.length; ++i) {
|
||||||
const { tagName, tagHash } = sortedTags[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);">`
|
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)
|
+ '<input type="checkbox" onclick="this.checked = !this.checked;"> ' + window.qBittorrent.Misc.escapeHtml(tagName)
|
||||||
+ '</a>'
|
+ "</a>"
|
||||||
});
|
});
|
||||||
if (i === 0)
|
if (i === 0)
|
||||||
el.addClass('separator');
|
el.addClass("separator");
|
||||||
contextTagList.appendChild(el);
|
contextTagList.appendChild(el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -513,19 +507,17 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
updateMenuItems: function() {
|
updateMenuItems: function() {
|
||||||
const id = Number(this.options.element.id);
|
const id = Number(this.options.element.id);
|
||||||
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
|
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
|
||||||
this.showItem('editCategory');
|
this.showItem("editCategory");
|
||||||
this.showItem('deleteCategory');
|
this.showItem("deleteCategory");
|
||||||
if (useSubcategories) {
|
if (useSubcategories)
|
||||||
this.showItem('createSubcategory');
|
this.showItem("createSubcategory");
|
||||||
}
|
else
|
||||||
else {
|
this.hideItem("createSubcategory");
|
||||||
this.hideItem('createSubcategory');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.hideItem('editCategory');
|
this.hideItem("editCategory");
|
||||||
this.hideItem('deleteCategory');
|
this.hideItem("deleteCategory");
|
||||||
this.hideItem('createSubcategory');
|
this.hideItem("createSubcategory");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -535,9 +527,9 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
updateMenuItems: function() {
|
updateMenuItems: function() {
|
||||||
const id = Number(this.options.element.id);
|
const id = Number(this.options.element.id);
|
||||||
if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
|
if ((id !== TAGS_ALL) && (id !== TAGS_UNTAGGED))
|
||||||
this.showItem('deleteTag');
|
this.showItem("deleteTag");
|
||||||
else
|
else
|
||||||
this.hideItem('deleteTag');
|
this.hideItem("deleteTag");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -547,82 +539,83 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
updateMenuItems: function() {
|
updateMenuItems: function() {
|
||||||
const enabledColumnIndex = function(text) {
|
const enabledColumnIndex = function(text) {
|
||||||
const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th");
|
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")
|
if (columns[i].get("html") === "Enabled")
|
||||||
return i;
|
return i;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.showItem('Enabled');
|
this.showItem("Enabled");
|
||||||
this.setItemChecked('Enabled', this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes");
|
this.setItemChecked("Enabled", this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes");
|
||||||
|
|
||||||
this.showItem('Uninstall');
|
this.showItem("Uninstall");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const RssFeedContextMenu = new Class({
|
const RssFeedContextMenu = new Class({
|
||||||
Extends: ContextMenu,
|
Extends: ContextMenu,
|
||||||
updateMenuItems: function() {
|
updateMenuItems: function() {
|
||||||
let selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
|
const selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds();
|
||||||
this.menu.getElement('a[href$=newSubscription]').parentNode.addClass('separator');
|
this.menu.getElement("a[href$=newSubscription]").parentNode.addClass("separator");
|
||||||
switch (selectedRows.length) {
|
switch (selectedRows.length) {
|
||||||
case 0:
|
case 0:
|
||||||
// remove separator on top of newSubscription entry to avoid double line
|
// 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
|
// menu when nothing selected
|
||||||
this.hideItem('update');
|
this.hideItem("update");
|
||||||
this.hideItem('markRead');
|
this.hideItem("markRead");
|
||||||
this.hideItem('rename');
|
this.hideItem("rename");
|
||||||
this.hideItem('delete');
|
this.hideItem("delete");
|
||||||
this.showItem('newSubscription');
|
this.showItem("newSubscription");
|
||||||
this.showItem('newFolder');
|
this.showItem("newFolder");
|
||||||
this.showItem('updateAll');
|
this.showItem("updateAll");
|
||||||
this.hideItem('copyFeedURL');
|
this.hideItem("copyFeedURL");
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
if (selectedRows[0] === 0) {
|
if (selectedRows[0] === 0) {
|
||||||
// menu when "unread" feed selected
|
// menu when "unread" feed selected
|
||||||
this.showItem('update');
|
this.showItem("update");
|
||||||
this.showItem('markRead');
|
this.showItem("markRead");
|
||||||
this.hideItem('rename');
|
this.hideItem("rename");
|
||||||
this.hideItem('delete');
|
this.hideItem("delete");
|
||||||
this.showItem('newSubscription');
|
this.showItem("newSubscription");
|
||||||
this.hideItem('newFolder');
|
this.hideItem("newFolder");
|
||||||
this.hideItem('updateAll');
|
this.hideItem("updateAll");
|
||||||
this.hideItem('copyFeedURL');
|
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
|
// menu when single folder selected
|
||||||
this.showItem('update');
|
this.showItem("update");
|
||||||
this.showItem('markRead');
|
this.showItem("markRead");
|
||||||
this.showItem('rename');
|
this.showItem("rename");
|
||||||
this.showItem('delete');
|
this.showItem("delete");
|
||||||
this.showItem('newSubscription');
|
this.showItem("newSubscription");
|
||||||
this.showItem('newFolder');
|
this.showItem("newFolder");
|
||||||
this.hideItem('updateAll');
|
this.hideItem("updateAll");
|
||||||
this.hideItem('copyFeedURL');
|
this.hideItem("copyFeedURL");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// menu when single feed selected
|
// menu when single feed selected
|
||||||
this.showItem('update');
|
this.showItem("update");
|
||||||
this.showItem('markRead');
|
this.showItem("markRead");
|
||||||
this.showItem('rename');
|
this.showItem("rename");
|
||||||
this.showItem('delete');
|
this.showItem("delete");
|
||||||
this.showItem('newSubscription');
|
this.showItem("newSubscription");
|
||||||
this.hideItem('newFolder');
|
this.hideItem("newFolder");
|
||||||
this.hideItem('updateAll');
|
this.hideItem("updateAll");
|
||||||
this.showItem('copyFeedURL');
|
this.showItem("copyFeedURL");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// menu when multiple items selected
|
// menu when multiple items selected
|
||||||
this.showItem('update');
|
this.showItem("update");
|
||||||
this.showItem('markRead');
|
this.showItem("markRead");
|
||||||
this.hideItem('rename');
|
this.hideItem("rename");
|
||||||
this.showItem('delete');
|
this.showItem("delete");
|
||||||
this.hideItem('newSubscription');
|
this.hideItem("newSubscription");
|
||||||
this.hideItem('newFolder');
|
this.hideItem("newFolder");
|
||||||
this.hideItem('updateAll');
|
this.hideItem("updateAll");
|
||||||
this.showItem('copyFeedURL');
|
this.showItem("copyFeedURL");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -639,12 +632,12 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
|
|
||||||
// draw the menu off-screen to know the menu dimensions
|
// draw the menu off-screen to know the menu dimensions
|
||||||
this.menu.setStyles({
|
this.menu.setStyles({
|
||||||
left: '-999em',
|
left: "-999em",
|
||||||
top: '-999em'
|
top: "-999em"
|
||||||
});
|
});
|
||||||
// position the menu
|
// position the menu
|
||||||
let xPosMenu = e.page.x + this.options.offsets.x - $('rssdownloaderpage').offsetLeft;
|
let xPosMenu = e.page.x + this.options.offsets.x - $("rssdownloaderpage").offsetLeft;
|
||||||
let yPosMenu = e.page.y + this.options.offsets.y - $('rssdownloaderpage').offsetTop;
|
let yPosMenu = e.page.y + this.options.offsets.y - $("rssdownloaderpage").offsetTop;
|
||||||
if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
|
if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth)
|
||||||
xPosMenu -= this.menu.offsetWidth;
|
xPosMenu -= this.menu.offsetWidth;
|
||||||
if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
|
if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight)
|
||||||
|
@ -655,31 +648,31 @@ window.qBittorrent.ContextMenu = (function() {
|
||||||
this.menu.setStyles({
|
this.menu.setStyles({
|
||||||
left: xPosMenu,
|
left: xPosMenu,
|
||||||
top: yPosMenu,
|
top: yPosMenu,
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
'z-index': '2000'
|
"z-index": "2000"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateMenuItems: function() {
|
updateMenuItems: function() {
|
||||||
let selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
|
const selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds();
|
||||||
this.showItem('addRule');
|
this.showItem("addRule");
|
||||||
switch (selectedRows.length) {
|
switch (selectedRows.length) {
|
||||||
case 0:
|
case 0:
|
||||||
// menu when nothing selected
|
// menu when nothing selected
|
||||||
this.hideItem('deleteRule');
|
this.hideItem("deleteRule");
|
||||||
this.hideItem('renameRule');
|
this.hideItem("renameRule");
|
||||||
this.hideItem('clearDownloadedEpisodes');
|
this.hideItem("clearDownloadedEpisodes");
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// menu when single item selected
|
// menu when single item selected
|
||||||
this.showItem('deleteRule');
|
this.showItem("deleteRule");
|
||||||
this.showItem('renameRule');
|
this.showItem("renameRule");
|
||||||
this.showItem('clearDownloadedEpisodes');
|
this.showItem("clearDownloadedEpisodes");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// menu when multiple items selected
|
// menu when multiple items selected
|
||||||
this.showItem('deleteRule');
|
this.showItem("deleteRule");
|
||||||
this.hideItem('renameRule');
|
this.hideItem("renameRule");
|
||||||
this.showItem('clearDownloadedEpisodes');
|
this.showItem("clearDownloadedEpisodes");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,10 @@
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.Download = (function() {
|
window.qBittorrent.Download = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -40,18 +39,21 @@ window.qBittorrent.Download = (function() {
|
||||||
|
|
||||||
const getCategories = function() {
|
const getCategories = function() {
|
||||||
new Request.JSON({
|
new Request.JSON({
|
||||||
url: 'api/v2/torrents/categories',
|
url: "api/v2/torrents/categories",
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onSuccess: function(data) {
|
onSuccess: function(data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
categories = data;
|
categories = data;
|
||||||
for (const i in data) {
|
for (const i in data) {
|
||||||
|
if (!Object.hasOwn(data, i))
|
||||||
|
continue;
|
||||||
|
|
||||||
const category = data[i];
|
const category = data[i];
|
||||||
const option = new Element("option");
|
const option = new Element("option");
|
||||||
option.set('value', category.name);
|
option.set("value", category.name);
|
||||||
option.set('html', category.name);
|
option.set("html", category.name);
|
||||||
$('categorySelect').appendChild(option);
|
$("categorySelect").appendChild(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,37 +64,31 @@ window.qBittorrent.Download = (function() {
|
||||||
const pref = window.parent.qBittorrent.Cache.preferences.get();
|
const pref = window.parent.qBittorrent.Cache.preferences.get();
|
||||||
|
|
||||||
defaultSavePath = pref.save_path;
|
defaultSavePath = pref.save_path;
|
||||||
$('savepath').setProperty('value', defaultSavePath);
|
$("savepath").setProperty("value", defaultSavePath);
|
||||||
$('startTorrent').checked = !pref.add_stopped_enabled;
|
$("startTorrent").checked = !pref.add_stopped_enabled;
|
||||||
$('addToTopOfQueue').checked = pref.add_to_top_of_queue;
|
$("addToTopOfQueue").checked = pref.add_to_top_of_queue;
|
||||||
|
|
||||||
if (pref.auto_tmm_enabled === 1) {
|
if (pref.auto_tmm_enabled) {
|
||||||
$('autoTMM').selectedIndex = 1;
|
$("autoTMM").selectedIndex = 1;
|
||||||
$('savepath').disabled = true;
|
$("savepath").disabled = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('autoTMM').selectedIndex = 0;
|
$("autoTMM").selectedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pref.torrent_stop_condition === "MetadataReceived") {
|
if (pref.torrent_stop_condition === "MetadataReceived")
|
||||||
$('stopCondition').selectedIndex = 1;
|
$("stopCondition").selectedIndex = 1;
|
||||||
}
|
else if (pref.torrent_stop_condition === "FilesChecked")
|
||||||
else if (pref.torrent_stop_condition === "FilesChecked") {
|
$("stopCondition").selectedIndex = 2;
|
||||||
$('stopCondition').selectedIndex = 2;
|
else
|
||||||
}
|
$("stopCondition").selectedIndex = 0;
|
||||||
else {
|
|
||||||
$('stopCondition').selectedIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pref.torrent_content_layout === "Subfolder") {
|
if (pref.torrent_content_layout === "Subfolder")
|
||||||
$('contentLayout').selectedIndex = 1;
|
$("contentLayout").selectedIndex = 1;
|
||||||
}
|
else if (pref.torrent_content_layout === "NoSubfolder")
|
||||||
else if (pref.torrent_content_layout === "NoSubfolder") {
|
$("contentLayout").selectedIndex = 2;
|
||||||
$('contentLayout').selectedIndex = 2;
|
else
|
||||||
}
|
$("contentLayout").selectedIndex = 0;
|
||||||
else {
|
|
||||||
$('contentLayout').selectedIndex = 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeCategorySelect = function(item) {
|
const changeCategorySelect = function(item) {
|
||||||
|
@ -101,41 +97,41 @@ window.qBittorrent.Download = (function() {
|
||||||
item.nextElementSibling.value = "";
|
item.nextElementSibling.value = "";
|
||||||
item.nextElementSibling.select();
|
item.nextElementSibling.select();
|
||||||
|
|
||||||
if ($('autoTMM').selectedIndex === 1)
|
if ($("autoTMM").selectedIndex === 1)
|
||||||
$('savepath').value = defaultSavePath;
|
$("savepath").value = defaultSavePath;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
item.nextElementSibling.hidden = true;
|
item.nextElementSibling.hidden = true;
|
||||||
const text = item.options[item.selectedIndex].textContent;
|
const text = item.options[item.selectedIndex].textContent;
|
||||||
item.nextElementSibling.value = text;
|
item.nextElementSibling.value = text;
|
||||||
|
|
||||||
if ($('autoTMM').selectedIndex === 1) {
|
if ($("autoTMM").selectedIndex === 1) {
|
||||||
const categoryName = item.value;
|
const categoryName = item.value;
|
||||||
const category = categories[categoryName];
|
const category = categories[categoryName];
|
||||||
let savePath = defaultSavePath;
|
let savePath = defaultSavePath;
|
||||||
if (category !== undefined)
|
if (category !== undefined)
|
||||||
savePath = (category['savePath'] !== "") ? category['savePath'] : `${defaultSavePath}/${categoryName}`;
|
savePath = (category["savePath"] !== "") ? category["savePath"] : `${defaultSavePath}/${categoryName}`;
|
||||||
$('savepath').value = savePath;
|
$("savepath").value = savePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeTMM = function(item) {
|
const changeTMM = function(item) {
|
||||||
if (item.selectedIndex === 1) {
|
if (item.selectedIndex === 1) {
|
||||||
$('savepath').disabled = true;
|
$("savepath").disabled = true;
|
||||||
|
|
||||||
const categorySelect = $('categorySelect');
|
const categorySelect = $("categorySelect");
|
||||||
const categoryName = categorySelect.options[categorySelect.selectedIndex].value;
|
const categoryName = categorySelect.options[categorySelect.selectedIndex].value;
|
||||||
const category = categories[categoryName];
|
const category = categories[categoryName];
|
||||||
$('savepath').value = (category === undefined) ? "" : category['savePath'];
|
$("savepath").value = (category === undefined) ? "" : category["savePath"];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('savepath').disabled = false;
|
$("savepath").disabled = false;
|
||||||
$('savepath').value = defaultSavePath;
|
$("savepath").value = defaultSavePath;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$(window).addEventListener("load", function() {
|
$(window).addEventListener("load", () => {
|
||||||
getPreferences();
|
getPreferences();
|
||||||
getCategories();
|
getCategories();
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.FileTree = (function() {
|
window.qBittorrent.FileTree = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -77,13 +76,12 @@ window.qBittorrent.FileTree = (function() {
|
||||||
|
|
||||||
generateNodeMap: function(node) {
|
generateNodeMap: function(node) {
|
||||||
// don't store root node in map
|
// don't store root node in map
|
||||||
if (node.root !== null) {
|
if (node.root !== null)
|
||||||
this.nodeMap[node.rowId] = node;
|
this.nodeMap[node.rowId] = node;
|
||||||
}
|
|
||||||
|
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
this.generateNodeMap(child);
|
this.generateNodeMap(child);
|
||||||
}.bind(this));
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getNode: function(rowId) {
|
getNode: function(rowId) {
|
||||||
|
@ -101,17 +99,17 @@ window.qBittorrent.FileTree = (function() {
|
||||||
*/
|
*/
|
||||||
toArray: function() {
|
toArray: function() {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
this.root.children.each(function(node) {
|
this.root.children.each((node) => {
|
||||||
this._getArrayOfNodes(node, nodes);
|
this._getArrayOfNodes(node, nodes);
|
||||||
}.bind(this));
|
});
|
||||||
return nodes;
|
return nodes;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getArrayOfNodes: function(node, array) {
|
_getArrayOfNodes: function(node, array) {
|
||||||
array.push(node);
|
array.push(node);
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
this._getArrayOfNodes(child, array);
|
this._getArrayOfNodes(child, array);
|
||||||
}.bind(this));
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -161,7 +159,7 @@ window.qBittorrent.FileTree = (function() {
|
||||||
|
|
||||||
let isFirstFile = true;
|
let isFirstFile = true;
|
||||||
|
|
||||||
this.children.each(function(node) {
|
this.children.each((node) => {
|
||||||
if (node.isFolder)
|
if (node.isFolder)
|
||||||
node.calculateSize();
|
node.calculateSize();
|
||||||
|
|
||||||
|
@ -185,7 +183,7 @@ window.qBittorrent.FileTree = (function() {
|
||||||
progress += (node.progress * node.size);
|
progress += (node.progress * node.size);
|
||||||
availability += (node.availability * node.size);
|
availability += (node.availability * node.size);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.remaining = remaining;
|
this.remaining = remaining;
|
||||||
|
|
|
@ -26,13 +26,12 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
// This file is the JavaScript implementation of base/utils/fs.cpp
|
// This file is the JavaScript implementation of base/utils/fs.cpp
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.Filesystem = (function() {
|
window.qBittorrent.Filesystem = (function() {
|
||||||
const exports = 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.
|
* Returns the file extension part of a file name.
|
||||||
*/
|
*/
|
||||||
const fileExtension = function(filename) {
|
const fileExtension = function(filename) {
|
||||||
const pointIndex = filename.lastIndexOf('.');
|
const pointIndex = filename.lastIndexOf(".");
|
||||||
if (pointIndex === -1)
|
if (pointIndex === -1)
|
||||||
return '';
|
return "";
|
||||||
return filename.substring(pointIndex + 1);
|
return filename.substring(pointIndex + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,7 +65,7 @@ window.qBittorrent.Filesystem = (function() {
|
||||||
const folderName = function(filepath) {
|
const folderName = function(filepath) {
|
||||||
const slashIndex = filepath.lastIndexOf(PathSeparator);
|
const slashIndex = filepath.lastIndexOf(PathSeparator);
|
||||||
if (slashIndex === -1)
|
if (slashIndex === -1)
|
||||||
return '';
|
return "";
|
||||||
return filepath.substring(0, slashIndex);
|
return filepath.substring(0, slashIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.LocalPreferences = (function() {
|
window.qBittorrent.LocalPreferences = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.Misc = (function() {
|
window.qBittorrent.Misc = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -86,8 +85,9 @@ window.qBittorrent.Misc = (function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret;
|
let ret;
|
||||||
if (i === 0)
|
if (i === 0) {
|
||||||
ret = value + " " + units[i];
|
ret = value + " " + units[i];
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
const precision = friendlyUnitPrecision(i);
|
const precision = friendlyUnitPrecision(i);
|
||||||
const offset = Math.pow(10, precision);
|
const offset = Math.pow(10, precision);
|
||||||
|
@ -112,18 +112,18 @@ window.qBittorrent.Misc = (function() {
|
||||||
return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]";
|
return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]";
|
||||||
let minutes = seconds / 60;
|
let minutes = seconds / 60;
|
||||||
if (minutes < 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;
|
let hours = minutes / 60;
|
||||||
minutes = minutes % 60;
|
minutes %= 60;
|
||||||
if (hours < 24)
|
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;
|
let days = hours / 24;
|
||||||
hours = hours % 24;
|
hours %= 24;
|
||||||
if (days < 365)
|
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;
|
const years = days / 365;
|
||||||
days = days % 365;
|
days %= 365;
|
||||||
return "QBT_TR(%1y %2d)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(years)).replace("%2", parseInt(days));
|
return "QBT_TR(%1y %2d)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(years)).replace("%2", Math.floor(days));
|
||||||
};
|
};
|
||||||
|
|
||||||
const friendlyPercentage = function(value) {
|
const friendlyPercentage = function(value) {
|
||||||
|
@ -152,7 +152,7 @@ window.qBittorrent.Misc = (function() {
|
||||||
valid: false
|
valid: false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof versionString !== 'string')
|
if (typeof versionString !== "string")
|
||||||
return failure;
|
return failure;
|
||||||
|
|
||||||
const tryToNumber = (str) => {
|
const tryToNumber = (str) => {
|
||||||
|
@ -160,7 +160,7 @@ window.qBittorrent.Misc = (function() {
|
||||||
return (isNaN(num) ? str : num);
|
return (isNaN(num) ? str : num);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ver = versionString.split('.', 4).map(val => tryToNumber(val));
|
const ver = versionString.split(".", 4).map(val => tryToNumber(val));
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
major: ver[0],
|
major: ver[0],
|
||||||
|
@ -171,7 +171,7 @@ window.qBittorrent.Misc = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const escapeHtml = function(str) {
|
const escapeHtml = function(str) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
div.appendChild(document.createTextNode(str));
|
div.appendChild(document.createTextNode(str));
|
||||||
const escapedString = div.innerHTML;
|
const escapedString = div.innerHTML;
|
||||||
div.remove();
|
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
|
// 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) {
|
const safeTrim = function(value) {
|
||||||
try {
|
try {
|
||||||
|
@ -206,9 +206,9 @@ window.qBittorrent.Misc = (function() {
|
||||||
*/
|
*/
|
||||||
const containsAllTerms = function(text, terms) {
|
const containsAllTerms = function(text, terms) {
|
||||||
const textToSearch = text.toLowerCase();
|
const textToSearch = text.toLowerCase();
|
||||||
return terms.every(function(term) {
|
return terms.every((term) => {
|
||||||
const isTermRequired = (term[0] === '+');
|
const isTermRequired = (term[0] === "+");
|
||||||
const isTermExcluded = (term[0] === '-');
|
const isTermExcluded = (term[0] === "-");
|
||||||
if (isTermRequired || isTermExcluded) {
|
if (isTermRequired || isTermExcluded) {
|
||||||
// ignore lonely +/-
|
// ignore lonely +/-
|
||||||
if (term.length === 1)
|
if (term.length === 1)
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
it in the onContentLoaded function of the new window.
|
it in the onContentLoaded function of the new window.
|
||||||
|
|
||||||
----------------------------------------------------------------- */
|
----------------------------------------------------------------- */
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass();
|
const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass();
|
||||||
|
|
||||||
|
@ -96,45 +96,43 @@ let exportTorrentFN = function() {};
|
||||||
const initializeWindows = function() {
|
const initializeWindows = function() {
|
||||||
saveWindowSize = function(windowId) {
|
saveWindowSize = function(windowId) {
|
||||||
const size = $(windowId).getSize();
|
const size = $(windowId).getSize();
|
||||||
LocalPreferences.set('window_' + windowId + '_width', size.x);
|
LocalPreferences.set("window_" + windowId + "_width", size.x);
|
||||||
LocalPreferences.set('window_' + windowId + '_height', size.y);
|
LocalPreferences.set("window_" + windowId + "_height", size.y);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadWindowWidth = function(windowId, defaultValue) {
|
loadWindowWidth = function(windowId, defaultValue) {
|
||||||
return LocalPreferences.get('window_' + windowId + '_width', defaultValue);
|
return LocalPreferences.get("window_" + windowId + "_width", defaultValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadWindowHeight = function(windowId, defaultValue) {
|
loadWindowHeight = function(windowId, defaultValue) {
|
||||||
return LocalPreferences.get('window_' + windowId + '_height', defaultValue);
|
return LocalPreferences.get("window_" + windowId + "_height", defaultValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
function addClickEvent(el, fn) {
|
function addClickEvent(el, fn) {
|
||||||
['Link', 'Button'].each(function(item) {
|
["Link", "Button"].each((item) => {
|
||||||
if ($(el + item)) {
|
if ($(el + item))
|
||||||
$(el + item).addEvent('click', fn);
|
$(el + item).addEvent("click", fn);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addClickEvent('download', function(e) {
|
addClickEvent("download", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
showDownloadPage();
|
showDownloadPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
showDownloadPage = function(urls) {
|
showDownloadPage = function(urls) {
|
||||||
const id = 'downloadPage';
|
const id = "downloadPage";
|
||||||
let contentUri = new URI('download.html');
|
const contentUri = new URI("download.html");
|
||||||
|
|
||||||
if (urls && (urls.length > 0)) {
|
if (urls && (urls.length > 0))
|
||||||
contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
|
contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
|
||||||
}
|
|
||||||
|
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: id,
|
id: id,
|
||||||
title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
|
title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: contentUri.toString(),
|
contentURL: contentUri.toString(),
|
||||||
addClass: 'windowFrame', // fixes iframe scrolling on iOS Safari
|
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
|
||||||
scrollbars: true,
|
scrollbars: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
closable: true,
|
closable: true,
|
||||||
|
@ -149,19 +147,19 @@ const initializeWindows = function() {
|
||||||
updateMainData();
|
updateMainData();
|
||||||
};
|
};
|
||||||
|
|
||||||
addClickEvent('preferences', function(e) {
|
addClickEvent("preferences", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const id = 'preferencesPage';
|
const id = "preferencesPage";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: id,
|
id: id,
|
||||||
title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
|
title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
|
||||||
loadMethod: 'xhr',
|
loadMethod: "xhr",
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
contentURL: new URI("views/preferences.html").toString(),
|
contentURL: new URI("views/preferences.html").toString(),
|
||||||
require: {
|
require: {
|
||||||
css: ['css/Tabs.css']
|
css: ["css/Tabs.css"]
|
||||||
},
|
},
|
||||||
toolbarURL: 'views/preferencesToolbar.html',
|
toolbarURL: "views/preferencesToolbar.html",
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
closable: true,
|
closable: true,
|
||||||
paddingVertical: 0,
|
paddingVertical: 0,
|
||||||
|
@ -174,15 +172,15 @@ const initializeWindows = function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addClickEvent('upload', function(e) {
|
addClickEvent("upload", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const id = 'uploadPage';
|
const id = "uploadPage";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: id,
|
id: id,
|
||||||
title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
|
title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("upload.html").toString(),
|
contentURL: new URI("upload.html").toString(),
|
||||||
addClass: 'windowFrame', // fixes iframe scrolling on iOS Safari
|
addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
|
||||||
scrollbars: true,
|
scrollbars: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
paddingVertical: 0,
|
paddingVertical: 0,
|
||||||
|
@ -198,9 +196,9 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
globalUploadLimitFN = function() {
|
globalUploadLimitFN = function() {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'uploadLimitPage',
|
id: "uploadLimitPage",
|
||||||
title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
|
contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
@ -216,9 +214,9 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'uploadLimitPage',
|
id: "uploadLimitPage",
|
||||||
title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
|
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(),
|
contentURL: new URI("uploadlimit.html").setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: 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
|
// if all torrents have same share ratio, display that share ratio. else use the default
|
||||||
const orig = torrentsHaveSameShareRatio ? shareRatio : "";
|
const orig = torrentsHaveSameShareRatio ? shareRatio : "";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'shareRatioPage',
|
id: "shareRatioPage",
|
||||||
title: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
|
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(),
|
contentURL: new URI("shareratio.html").setData("hashes", hashes.join("|")).setData("orig", orig).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
paddingVertical: 0,
|
paddingVertical: 0,
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
width: 424,
|
width: 424,
|
||||||
height: 175
|
height: 200
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -275,8 +273,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/toggleSequentialDownload',
|
url: "api/v2/torrents/toggleSequentialDownload",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -289,8 +287,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/toggleFirstLastPiecePrio',
|
url: "api/v2/torrents/toggleFirstLastPiecePrio",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -303,8 +301,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setSuperSeeding',
|
url: "api/v2/torrents/setSuperSeeding",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
value: val,
|
value: val,
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
|
@ -318,10 +316,10 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setForceStart',
|
url: "api/v2/torrents/setForceStart",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
value: 'true',
|
value: "true",
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -331,9 +329,9 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
globalDownloadLimitFN = function() {
|
globalDownloadLimitFN = function() {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'downloadLimitPage',
|
id: "downloadLimitPage",
|
||||||
title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
|
contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
@ -346,11 +344,11 @@ const initializeWindows = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
StatisticsLinkFN = function() {
|
StatisticsLinkFN = function() {
|
||||||
const id = 'statisticspage';
|
const id = "statisticspage";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: id,
|
id: id,
|
||||||
title: 'QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]',
|
title: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
|
||||||
loadMethod: 'xhr',
|
loadMethod: "xhr",
|
||||||
contentURL: new URI("views/statistics.html").toString(),
|
contentURL: new URI("views/statistics.html").toString(),
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
|
@ -366,9 +364,9 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'downloadLimitPage',
|
id: "downloadLimitPage",
|
||||||
title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
|
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(),
|
contentURL: new URI("downloadlimit.html").setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
|
@ -385,9 +383,9 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'confirmDeletionPage',
|
id: "confirmDeletionPage",
|
||||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
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(),
|
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).setData("deleteFiles", deleteFiles).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -400,7 +398,7 @@ const initializeWindows = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addClickEvent('delete', function(e) {
|
addClickEvent("delete", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
deleteFN();
|
deleteFN();
|
||||||
});
|
});
|
||||||
|
@ -409,8 +407,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/stop',
|
url: "api/v2/torrents/stop",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -423,8 +421,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/start',
|
url: "api/v2/torrents/start",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -437,14 +435,14 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
let enable = false;
|
let enable = false;
|
||||||
hashes.each(function(hash, index) {
|
hashes.each((hash, index) => {
|
||||||
const row = torrentsTable.rows[hash];
|
const row = torrentsTable.rows[hash];
|
||||||
if (!row.full_data.auto_tmm)
|
if (!row.full_data.auto_tmm)
|
||||||
enable = true;
|
enable = true;
|
||||||
});
|
});
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setAutoManagement',
|
url: "api/v2/torrents/setAutoManagement",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
enable: enable
|
enable: enable
|
||||||
|
@ -458,8 +456,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/recheck',
|
url: "api/v2/torrents/recheck",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
}
|
}
|
||||||
|
@ -472,8 +470,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/reannounce',
|
url: "api/v2/torrents/reannounce",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
}
|
}
|
||||||
|
@ -489,10 +487,10 @@ const initializeWindows = function() {
|
||||||
const row = torrentsTable.rows[hash];
|
const row = torrentsTable.rows[hash];
|
||||||
|
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'setLocationPage',
|
id: "setLocationPage",
|
||||||
title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
|
title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("setlocation.html").setData("hashes", hashes.join('|')).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
|
contentURL: new URI("setlocation.html").setData("hashes", hashes.join("|")).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -511,9 +509,9 @@ const initializeWindows = function() {
|
||||||
const row = torrentsTable.rows[hash];
|
const row = torrentsTable.rows[hash];
|
||||||
if (row) {
|
if (row) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'renamePage',
|
id: "renamePage",
|
||||||
title: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
|
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(),
|
contentURL: new URI("rename.html").setData("hash", hash).setData("name", row.full_data.name).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -534,11 +532,11 @@ const initializeWindows = function() {
|
||||||
const row = torrentsTable.rows[hash];
|
const row = torrentsTable.rows[hash];
|
||||||
if (row) {
|
if (row) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'multiRenamePage',
|
id: "multiRenamePage",
|
||||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
|
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
|
||||||
data: { hash: hash, selectedRows: [] },
|
data: { hash: hash, selectedRows: [] },
|
||||||
loadMethod: 'xhr',
|
loadMethod: "xhr",
|
||||||
contentURL: 'rename_files.html',
|
contentURL: "rename_files.html",
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -546,7 +544,7 @@ const initializeWindows = function() {
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 420,
|
height: 420,
|
||||||
resizeLimit: { 'x': [800], 'y': [420] }
|
resizeLimit: { "x": [800], "y": [420] }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,10 +555,10 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newCategoryPage',
|
id: "newCategoryPage",
|
||||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
|
title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join('|')).toString(),
|
contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -579,10 +577,10 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
const categoryName = category_list.has(categoryHash)
|
const categoryName = category_list.has(categoryHash)
|
||||||
? category_list.get(categoryHash).name
|
? category_list.get(categoryHash).name
|
||||||
: '';
|
: "";
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/setCategory',
|
url: "api/v2/torrents/setCategory",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
category: categoryName
|
category: categoryName
|
||||||
|
@ -593,9 +591,9 @@ const initializeWindows = function() {
|
||||||
createCategoryFN = function() {
|
createCategoryFN = function() {
|
||||||
const action = "create";
|
const action = "create";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newCategoryPage',
|
id: "newCategoryPage",
|
||||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("newcategory.html").setData("action", action).toString(),
|
contentURL: new URI("newcategory.html").setData("action", action).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -612,9 +610,9 @@ const initializeWindows = function() {
|
||||||
const action = "createSubcategory";
|
const action = "createSubcategory";
|
||||||
const categoryName = category_list.get(categoryHash).name + "/";
|
const categoryName = category_list.get(categoryHash).name + "/";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newSubcategoryPage',
|
id: "newSubcategoryPage",
|
||||||
title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
|
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(),
|
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -631,10 +629,10 @@ const initializeWindows = function() {
|
||||||
const action = "edit";
|
const action = "edit";
|
||||||
const category = category_list.get(categoryHash);
|
const category = category_list.get(categoryHash);
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'editCategoryPage',
|
id: "editCategoryPage",
|
||||||
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
|
title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI('newcategory.html').setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
|
contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -649,8 +647,8 @@ const initializeWindows = function() {
|
||||||
removeCategoryFN = function(categoryHash) {
|
removeCategoryFN = function(categoryHash) {
|
||||||
const categoryName = category_list.get(categoryHash).name;
|
const categoryName = category_list.get(categoryHash).name;
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/removeCategories',
|
url: "api/v2/torrents/removeCategories",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
categories: categoryName
|
categories: categoryName
|
||||||
}
|
}
|
||||||
|
@ -661,26 +659,26 @@ const initializeWindows = function() {
|
||||||
deleteUnusedCategoriesFN = function() {
|
deleteUnusedCategoriesFN = function() {
|
||||||
const categories = [];
|
const categories = [];
|
||||||
category_list.forEach((category, hash) => {
|
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);
|
categories.push(category.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/removeCategories',
|
url: "api/v2/torrents/removeCategories",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
categories: categories.join('\n')
|
categories: categories.join("\n")
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
setCategoryFilter(CATEGORIES_ALL);
|
setCategoryFilter(CATEGORIES_ALL);
|
||||||
};
|
};
|
||||||
|
|
||||||
startTorrentsByCategoryFN = function(categoryHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/start',
|
url: "api/v2/torrents/start",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -690,11 +688,11 @@ const initializeWindows = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
stopTorrentsByCategoryFN = function(categoryHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/stop',
|
url: "api/v2/torrents/stop",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -704,12 +702,12 @@ const initializeWindows = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteTorrentsByCategoryFN = function(categoryHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'confirmDeletionPage',
|
id: "confirmDeletionPage",
|
||||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -727,9 +725,9 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newTagPage',
|
id: "newTagPage",
|
||||||
title: "QBT_TR(Add Tags)QBT_TR[CONTEXT=TransferListWidget]",
|
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(),
|
contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -747,10 +745,10 @@ const initializeWindows = function() {
|
||||||
if (hashes.length <= 0)
|
if (hashes.length <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : '';
|
const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : "";
|
||||||
new Request({
|
new Request({
|
||||||
url: (isSet ? 'api/v2/torrents/addTags' : 'api/v2/torrents/removeTags'),
|
url: (isSet ? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"),
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
tags: tagName,
|
tags: tagName,
|
||||||
|
@ -762,8 +760,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: ('api/v2/torrents/removeTags'),
|
url: ("api/v2/torrents/removeTags"),
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|"),
|
hashes: hashes.join("|"),
|
||||||
}
|
}
|
||||||
|
@ -774,9 +772,9 @@ const initializeWindows = function() {
|
||||||
createTagFN = function() {
|
createTagFN = function() {
|
||||||
const action = "create";
|
const action = "create";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'newTagPage',
|
id: "newTagPage",
|
||||||
title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
|
title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("newtag.html").setData("action", action).toString(),
|
contentURL: new URI("newtag.html").setData("action", action).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -792,8 +790,8 @@ const initializeWindows = function() {
|
||||||
removeTagFN = function(tagHash) {
|
removeTagFN = function(tagHash) {
|
||||||
const tagName = tagList.get(tagHash).name;
|
const tagName = tagList.get(tagHash).name;
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/deleteTags',
|
url: "api/v2/torrents/deleteTags",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
tags: tagName
|
tags: tagName
|
||||||
}
|
}
|
||||||
|
@ -804,25 +802,25 @@ const initializeWindows = function() {
|
||||||
deleteUnusedTagsFN = function() {
|
deleteUnusedTagsFN = function() {
|
||||||
const tags = [];
|
const tags = [];
|
||||||
tagList.forEach((tag, hash) => {
|
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);
|
tags.push(tag.name);
|
||||||
});
|
});
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/deleteTags',
|
url: "api/v2/torrents/deleteTags",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
tags: tags.join(',')
|
tags: tags.join(",")
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
setTagFilter(TAGS_ALL);
|
setTagFilter(TAGS_ALL);
|
||||||
};
|
};
|
||||||
|
|
||||||
startTorrentsByTagFN = function(tagHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/start',
|
url: "api/v2/torrents/start",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -832,11 +830,11 @@ const initializeWindows = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
stopTorrentsByTagFN = function(tagHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/stop',
|
url: "api/v2/torrents/stop",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -846,12 +844,12 @@ const initializeWindows = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteTorrentsByTagFN = function(tagHash) {
|
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) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'confirmDeletionPage',
|
id: "confirmDeletionPage",
|
||||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -869,17 +867,16 @@ const initializeWindows = function() {
|
||||||
let hashes = [];
|
let hashes = [];
|
||||||
switch (trackerHashInt) {
|
switch (trackerHashInt) {
|
||||||
case TRACKERS_ALL:
|
case TRACKERS_ALL:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||||
break;
|
break;
|
||||||
case TRACKERS_TRACKERLESS:
|
case TRACKERS_TRACKERLESS:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
const uniqueTorrents = new Set();
|
const uniqueTorrents = new Set();
|
||||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||||
for (const torrent of torrents) {
|
for (const torrent of torrents)
|
||||||
uniqueTorrents.add(torrent);
|
uniqueTorrents.add(torrent);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hashes = [...uniqueTorrents];
|
hashes = [...uniqueTorrents];
|
||||||
break;
|
break;
|
||||||
|
@ -888,8 +885,8 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
if (hashes.length > 0) {
|
if (hashes.length > 0) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/start',
|
url: "api/v2/torrents/start",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -903,17 +900,16 @@ const initializeWindows = function() {
|
||||||
let hashes = [];
|
let hashes = [];
|
||||||
switch (trackerHashInt) {
|
switch (trackerHashInt) {
|
||||||
case TRACKERS_ALL:
|
case TRACKERS_ALL:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||||
break;
|
break;
|
||||||
case TRACKERS_TRACKERLESS:
|
case TRACKERS_TRACKERLESS:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
const uniqueTorrents = new Set();
|
const uniqueTorrents = new Set();
|
||||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||||
for (const torrent of torrents) {
|
for (const torrent of torrents)
|
||||||
uniqueTorrents.add(torrent);
|
uniqueTorrents.add(torrent);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hashes = [...uniqueTorrents];
|
hashes = [...uniqueTorrents];
|
||||||
break;
|
break;
|
||||||
|
@ -922,8 +918,8 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/stop',
|
url: "api/v2/torrents/stop",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -937,17 +933,16 @@ const initializeWindows = function() {
|
||||||
let hashes = [];
|
let hashes = [];
|
||||||
switch (trackerHashInt) {
|
switch (trackerHashInt) {
|
||||||
case TRACKERS_ALL:
|
case TRACKERS_ALL:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
|
||||||
break;
|
break;
|
||||||
case TRACKERS_TRACKERLESS:
|
case TRACKERS_TRACKERLESS:
|
||||||
hashes = torrentsTable.getFilteredTorrentsHashes('all', CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
const uniqueTorrents = new Set();
|
const uniqueTorrents = new Set();
|
||||||
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
|
||||||
for (const torrent of torrents) {
|
for (const torrent of torrents)
|
||||||
uniqueTorrents.add(torrent);
|
uniqueTorrents.add(torrent);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hashes = [...uniqueTorrents];
|
hashes = [...uniqueTorrents];
|
||||||
break;
|
break;
|
||||||
|
@ -956,9 +951,9 @@ const initializeWindows = function() {
|
||||||
|
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'confirmDeletionPage',
|
id: "confirmDeletionPage",
|
||||||
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
@ -1068,13 +1063,13 @@ const initializeWindows = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addClickEvent('stopAll', (e) => {
|
addClickEvent("stopAll", (e) => {
|
||||||
new Event(e).stop();
|
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({
|
new Request({
|
||||||
url: 'api/v2/torrents/stop',
|
url: "api/v2/torrents/stop",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: "all"
|
hashes: "all"
|
||||||
}
|
}
|
||||||
|
@ -1083,13 +1078,13 @@ const initializeWindows = function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addClickEvent('startAll', (e) => {
|
addClickEvent("startAll", (e) => {
|
||||||
new Event(e).stop();
|
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({
|
new Request({
|
||||||
url: 'api/v2/torrents/start',
|
url: "api/v2/torrents/start",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: "all"
|
hashes: "all"
|
||||||
}
|
}
|
||||||
|
@ -1098,15 +1093,15 @@ const initializeWindows = function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
['stop', 'start', 'recheck'].each(function(item) {
|
["stop", "start", "recheck"].each((item) => {
|
||||||
addClickEvent(item, function(e) {
|
addClickEvent(item, (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
hashes.each(function(hash, index) {
|
hashes.each((hash, index) => {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/' + item,
|
url: "api/v2/torrents/" + item,
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hash
|
hashes: hash
|
||||||
}
|
}
|
||||||
|
@ -1117,8 +1112,8 @@ const initializeWindows = function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
['decreasePrio', 'increasePrio', 'topPrio', 'bottomPrio'].each(function(item) {
|
["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item) => {
|
||||||
addClickEvent(item, function(e) {
|
addClickEvent(item, (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
setQueuePositionFN(item);
|
setQueuePositionFN(item);
|
||||||
});
|
});
|
||||||
|
@ -1128,8 +1123,8 @@ const initializeWindows = function() {
|
||||||
const hashes = torrentsTable.selectedRowsIds();
|
const hashes = torrentsTable.selectedRowsIds();
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/' + cmd,
|
url: "api/v2/torrents/" + cmd,
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
hashes: hashes.join("|")
|
hashes: hashes.join("|")
|
||||||
}
|
}
|
||||||
|
@ -1138,19 +1133,19 @@ const initializeWindows = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addClickEvent('about', function(e) {
|
addClickEvent("about", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
const id = 'aboutpage';
|
const id = "aboutpage";
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: id,
|
id: id,
|
||||||
title: 'QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]',
|
title: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
|
||||||
loadMethod: 'xhr',
|
loadMethod: "xhr",
|
||||||
contentURL: new URI("views/about.html").toString(),
|
contentURL: new URI("views/about.html").toString(),
|
||||||
require: {
|
require: {
|
||||||
css: ['css/Tabs.css']
|
css: ["css/Tabs.css"]
|
||||||
},
|
},
|
||||||
toolbar: true,
|
toolbar: true,
|
||||||
toolbarURL: 'views/aboutToolbar.html',
|
toolbarURL: "views/aboutToolbar.html",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
width: loadWindowWidth(id, 550),
|
width: loadWindowWidth(id, 550),
|
||||||
height: loadWindowHeight(id, 360),
|
height: loadWindowHeight(id, 360),
|
||||||
|
@ -1160,25 +1155,25 @@ const initializeWindows = function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addClickEvent('logout', function(e) {
|
addClickEvent("logout", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/auth/logout',
|
url: "api/v2/auth/logout",
|
||||||
method: 'post',
|
method: "post",
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
addClickEvent('shutdown', function(e) {
|
addClickEvent("shutdown", (e) => {
|
||||||
new Event(e).stop();
|
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({
|
new Request({
|
||||||
url: 'api/v2/app/shutdown',
|
url: "api/v2/app/shutdown",
|
||||||
method: 'post',
|
method: "post",
|
||||||
onSuccess: function() {
|
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.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();
|
document.close();
|
||||||
window.stop();
|
window.stop();
|
||||||
|
@ -1189,8 +1184,8 @@ const initializeWindows = function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deactivate menu header links
|
// Deactivate menu header links
|
||||||
$$('a.returnFalse').each(function(el) {
|
$$("a.returnFalse").each((el) => {
|
||||||
el.addEvent('click', function(e) {
|
el.addEvent("click", (e) => {
|
||||||
new Event(e).stop();
|
new Event(e).stop();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.PiecesBar = (() => {
|
window.qBittorrent.PiecesBar = (() => {
|
||||||
const exports = () => {
|
const exports = () => {
|
||||||
|
@ -51,35 +50,35 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
const PiecesBar = new Class({
|
const PiecesBar = new Class({
|
||||||
initialize(pieces, parameters) {
|
initialize(pieces, parameters) {
|
||||||
const vals = {
|
const vals = {
|
||||||
'id': 'piecesbar_' + (piecesBarUniqueId++),
|
"id": "piecesbar_" + (piecesBarUniqueId++),
|
||||||
'width': 0,
|
"width": 0,
|
||||||
'height': 0,
|
"height": 0,
|
||||||
'downloadingColor': 'hsl(110deg 94% 27%)', // @TODO palette vars not supported for this value, apply average
|
"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
|
"haveColor": "hsl(210deg 55% 55%)", // @TODO palette vars not supported for this value, apply average
|
||||||
'borderSize': 1,
|
"borderSize": 1,
|
||||||
'borderColor': 'var(--color-border-default)'
|
"borderColor": "var(--color-border-default)"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parameters && (typeOf(parameters) === 'object'))
|
if (parameters && (typeOf(parameters) === "object"))
|
||||||
Object.append(vals, parameters);
|
Object.append(vals, parameters);
|
||||||
vals.height = Math.max(vals.height, 12);
|
vals.height = Math.max(vals.height, 12);
|
||||||
|
|
||||||
const obj = new Element('div', {
|
const obj = new Element("div", {
|
||||||
'id': vals.id,
|
"id": vals.id,
|
||||||
'class': 'piecesbarWrapper',
|
"class": "piecesbarWrapper",
|
||||||
'styles': {
|
"styles": {
|
||||||
'border': vals.borderSize.toString() + 'px solid ' + vals.borderColor,
|
"border": vals.borderSize.toString() + "px solid " + vals.borderColor,
|
||||||
'height': vals.height.toString() + 'px',
|
"height": vals.height.toString() + "px",
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
obj.vals = vals;
|
obj.vals = vals;
|
||||||
obj.vals.pieces = [pieces, []].pick();
|
obj.vals.pieces = [pieces, []].pick();
|
||||||
|
|
||||||
obj.vals.canvas = new Element('canvas', {
|
obj.vals.canvas = new Element("canvas", {
|
||||||
'id': vals.id + '_canvas',
|
"id": vals.id + "_canvas",
|
||||||
'class': 'piecesbarCanvas',
|
"class": "piecesbarCanvas",
|
||||||
'width': (vals.width - (2 * vals.borderSize)).toString(),
|
"width": (vals.width - (2 * vals.borderSize)).toString(),
|
||||||
'height': '1' // will stretch vertically to take up the height of the parent
|
"height": "1" // will stretch vertically to take up the height of the parent
|
||||||
});
|
});
|
||||||
obj.appendChild(obj.vals.canvas);
|
obj.appendChild(obj.vals.canvas);
|
||||||
|
|
||||||
|
@ -124,7 +123,7 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
this.vals.canvas.width = width - (2 * this.vals.borderSize);
|
this.vals.canvas.width = width - (2 * this.vals.borderSize);
|
||||||
|
|
||||||
const canvas = this.vals.canvas;
|
const canvas = this.vals.canvas;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
const imageWidth = canvas.width;
|
const imageWidth = canvas.width;
|
||||||
|
@ -136,13 +135,10 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
let maxStatus = 0;
|
let maxStatus = 0;
|
||||||
|
|
||||||
for (const status of pieces) {
|
for (const status of pieces) {
|
||||||
if (status > maxStatus) {
|
if (status > maxStatus)
|
||||||
maxStatus = status;
|
maxStatus = status;
|
||||||
}
|
if (status < minStatus)
|
||||||
|
|
||||||
if (status < minStatus) {
|
|
||||||
minStatus = status;
|
minStatus = status;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no progress then don't do anything
|
// if no progress then don't do anything
|
||||||
|
@ -220,15 +216,13 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
statusValues[STATUS_DOWNLOADING] = Math.min(statusValues[STATUS_DOWNLOADING], 1);
|
statusValues[STATUS_DOWNLOADING] = Math.min(statusValues[STATUS_DOWNLOADING], 1);
|
||||||
statusValues[STATUS_DOWNLOADED] = Math.min(statusValues[STATUS_DOWNLOADED], 1);
|
statusValues[STATUS_DOWNLOADED] = Math.min(statusValues[STATUS_DOWNLOADED], 1);
|
||||||
|
|
||||||
if (!lastValue) {
|
if (!lastValue)
|
||||||
lastValue = statusValues;
|
lastValue = statusValues;
|
||||||
}
|
|
||||||
|
|
||||||
// group contiguous colors together and draw as a single rectangle
|
// group contiguous colors together and draw as a single rectangle
|
||||||
if ((lastValue[STATUS_DOWNLOADING] === statusValues[STATUS_DOWNLOADING])
|
if ((lastValue[STATUS_DOWNLOADING] === statusValues[STATUS_DOWNLOADING])
|
||||||
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED])) {
|
&& (lastValue[STATUS_DOWNLOADED] === statusValues[STATUS_DOWNLOADED]))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
const rectangleWidth = x - rectangleStart;
|
const rectangleWidth = x - rectangleStart;
|
||||||
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
this._drawStatus(ctx, rectangleStart, rectangleWidth, lastValue);
|
||||||
|
@ -246,7 +240,7 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
|
|
||||||
function drawStatus(ctx, start, width, statusValues) {
|
function drawStatus(ctx, start, width, statusValues) {
|
||||||
// mix the colors by using transparency and a composite mode
|
// mix the colors by using transparency and a composite mode
|
||||||
ctx.globalCompositeOperation = 'lighten';
|
ctx.globalCompositeOperation = "lighten";
|
||||||
|
|
||||||
if (statusValues[STATUS_DOWNLOADING]) {
|
if (statusValues[STATUS_DOWNLOADING]) {
|
||||||
ctx.globalAlpha = statusValues[STATUS_DOWNLOADING];
|
ctx.globalAlpha = statusValues[STATUS_DOWNLOADING];
|
||||||
|
@ -266,7 +260,7 @@ window.qBittorrent.PiecesBar = (() => {
|
||||||
if (!obj)
|
if (!obj)
|
||||||
return;
|
return;
|
||||||
if (!obj.parentNode)
|
if (!obj.parentNode)
|
||||||
return setTimeout(function() { checkForParent(id); }, 1);
|
return setTimeout(() => { checkForParent(id); }, 1);
|
||||||
|
|
||||||
obj.refresh();
|
obj.refresh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.ProgressBar = (function() {
|
window.qBittorrent.ProgressBar = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -43,60 +42,60 @@ window.qBittorrent.ProgressBar = (function() {
|
||||||
const ProgressBar = new Class({
|
const ProgressBar = new Class({
|
||||||
initialize: function(value, parameters) {
|
initialize: function(value, parameters) {
|
||||||
const vals = {
|
const vals = {
|
||||||
'id': 'progressbar_' + (ProgressBars++),
|
"id": "progressbar_" + (ProgressBars++),
|
||||||
'value': [value, 0].pick(),
|
"value": [value, 0].pick(),
|
||||||
'width': 0,
|
"width": 0,
|
||||||
'height': 0,
|
"height": 0,
|
||||||
'darkbg': 'var(--color-background-blue)',
|
"darkbg": "var(--color-background-blue)",
|
||||||
'darkfg': 'var(--color-text-white)',
|
"darkfg": "var(--color-text-white)",
|
||||||
'lightbg': 'var(--color-background-default)',
|
"lightbg": "var(--color-background-default)",
|
||||||
'lightfg': 'var(--color-text-default)'
|
"lightfg": "var(--color-text-default)"
|
||||||
};
|
};
|
||||||
if (parameters && (typeOf(parameters) === 'object'))
|
if (parameters && (typeOf(parameters) === "object"))
|
||||||
Object.append(vals, parameters);
|
Object.append(vals, parameters);
|
||||||
if (vals.height < 12)
|
if (vals.height < 12)
|
||||||
vals.height = 12;
|
vals.height = 12;
|
||||||
const obj = new Element('div', {
|
const obj = new Element("div", {
|
||||||
'id': vals.id,
|
"id": vals.id,
|
||||||
'class': 'progressbar_wrapper',
|
"class": "progressbar_wrapper",
|
||||||
'styles': {
|
"styles": {
|
||||||
'border': '1px solid var(--color-border-default)',
|
"border": "1px solid var(--color-border-default)",
|
||||||
'width': vals.width,
|
"width": vals.width,
|
||||||
'height': vals.height,
|
"height": vals.height,
|
||||||
'position': 'relative',
|
"position": "relative",
|
||||||
'margin': '0 auto'
|
"margin": "0 auto"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
obj.vals = vals;
|
obj.vals = vals;
|
||||||
obj.vals.value = [value, 0].pick();
|
obj.vals.value = [value, 0].pick();
|
||||||
obj.vals.dark = new Element('div', {
|
obj.vals.dark = new Element("div", {
|
||||||
'id': vals.id + '_dark',
|
"id": vals.id + "_dark",
|
||||||
'class': 'progressbar_dark',
|
"class": "progressbar_dark",
|
||||||
'styles': {
|
"styles": {
|
||||||
'width': vals.width,
|
"width": vals.width,
|
||||||
'height': vals.height,
|
"height": vals.height,
|
||||||
'background': vals.darkbg,
|
"background": vals.darkbg,
|
||||||
'color': vals.darkfg,
|
"color": vals.darkfg,
|
||||||
'position': 'absolute',
|
"position": "absolute",
|
||||||
'text-align': 'center',
|
"text-align": "center",
|
||||||
'left': 0,
|
"left": 0,
|
||||||
'top': 0,
|
"top": 0,
|
||||||
'line-height': vals.height
|
"line-height": vals.height
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
obj.vals.light = new Element('div', {
|
obj.vals.light = new Element("div", {
|
||||||
'id': vals.id + '_light',
|
"id": vals.id + "_light",
|
||||||
'class': 'progressbar_light',
|
"class": "progressbar_light",
|
||||||
'styles': {
|
"styles": {
|
||||||
'width': vals.width,
|
"width": vals.width,
|
||||||
'height': vals.height,
|
"height": vals.height,
|
||||||
'background': vals.lightbg,
|
"background": vals.lightbg,
|
||||||
'color': vals.lightfg,
|
"color": vals.lightfg,
|
||||||
'position': 'absolute',
|
"position": "absolute",
|
||||||
'text-align': 'center',
|
"text-align": "center",
|
||||||
'left': 0,
|
"left": 0,
|
||||||
'top': 0,
|
"top": 0,
|
||||||
'line-height': vals.height
|
"line-height": vals.height
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
obj.appendChild(obj.vals.dark);
|
obj.appendChild(obj.vals.dark);
|
||||||
|
@ -120,26 +119,24 @@ window.qBittorrent.ProgressBar = (function() {
|
||||||
value = parseFloat(value);
|
value = parseFloat(value);
|
||||||
if (isNaN(value))
|
if (isNaN(value))
|
||||||
value = 0;
|
value = 0;
|
||||||
if (value > 100)
|
value = Math.min(Math.max(value, 0), 100);
|
||||||
value = 100;
|
|
||||||
if (value < 0)
|
|
||||||
value = 0;
|
|
||||||
this.vals.value = value;
|
this.vals.value = value;
|
||||||
this.vals.dark.empty();
|
|
||||||
this.vals.light.empty();
|
const displayedValue = `${value.round(1).toFixed(1)}%`;
|
||||||
this.vals.dark.appendText(value.round(1).toFixed(1) + '%');
|
this.vals.dark.textContent = displayedValue;
|
||||||
this.vals.light.appendText(value.round(1).toFixed(1) + '%');
|
this.vals.light.textContent = displayedValue;
|
||||||
const r = parseInt(this.vals.width * (value / 100));
|
|
||||||
this.vals.dark.setStyle('clip', 'rect(0,' + r + 'px,' + this.vals.height + 'px,0)');
|
const r = parseInt((this.vals.width * (value / 100)), 10);
|
||||||
this.vals.light.setStyle('clip', 'rect(0,' + this.vals.width + 'px,' + this.vals.height + 'px,' + r + 'px)');
|
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) {
|
function ProgressBar_setWidth(value) {
|
||||||
if (this.vals.width !== value) {
|
if (this.vals.width !== value) {
|
||||||
this.vals.width = value;
|
this.vals.width = value;
|
||||||
this.setStyle('width', value);
|
this.setStyle("width", value);
|
||||||
this.vals.dark.setStyle('width', value);
|
this.vals.dark.setStyle("width", value);
|
||||||
this.vals.light.setStyle('width', value);
|
this.vals.light.setStyle("width", value);
|
||||||
this.setValue(this.vals.value);
|
this.setValue(this.vals.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,10 +147,10 @@ window.qBittorrent.ProgressBar = (function() {
|
||||||
return;
|
return;
|
||||||
if (!obj.parentNode)
|
if (!obj.parentNode)
|
||||||
return setTimeout('ProgressBar_checkForParent("' + id + '")', 1);
|
return setTimeout('ProgressBar_checkForParent("' + id + '")', 1);
|
||||||
obj.setStyle('width', '100%');
|
obj.setStyle("width", "100%");
|
||||||
const w = obj.offsetWidth;
|
const w = obj.offsetWidth;
|
||||||
obj.vals.dark.setStyle('width', w);
|
obj.vals.dark.setStyle("width", w);
|
||||||
obj.vals.light.setStyle('width', w);
|
obj.vals.light.setStyle("width", w);
|
||||||
obj.vals.width = w;
|
obj.vals.width = w;
|
||||||
obj.setValue(obj.vals.value);
|
obj.setValue(obj.vals.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.PropFiles = (function() {
|
window.qBittorrent.PropFiles = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -82,7 +81,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const getChildFiles = function(node) {
|
const getChildFiles = function(node) {
|
||||||
if (node.isFolder) {
|
if (node.isFolder) {
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
getChildFiles(child);
|
getChildFiles(child);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,7 +91,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
getChildFiles(child);
|
getChildFiles(child);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,8 +106,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const checkbox = e.target;
|
const checkbox = e.target;
|
||||||
const priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored;
|
const priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored;
|
||||||
const id = checkbox.get('data-id');
|
const id = checkbox.get("data-id");
|
||||||
const fileId = checkbox.get('data-file-id');
|
const fileId = checkbox.get("data-file-id");
|
||||||
|
|
||||||
const rows = getAllChildren(id, fileId);
|
const rows = getAllChildren(id, fileId);
|
||||||
|
|
||||||
|
@ -119,8 +118,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const fileComboboxChanged = function(e) {
|
const fileComboboxChanged = function(e) {
|
||||||
const combobox = e.target;
|
const combobox = e.target;
|
||||||
const priority = combobox.value;
|
const priority = combobox.value;
|
||||||
const id = combobox.get('data-id');
|
const id = combobox.get("data-id");
|
||||||
const fileId = combobox.get('data-file-id');
|
const fileId = combobox.get("data-file-id");
|
||||||
|
|
||||||
const rows = getAllChildren(id, fileId);
|
const rows = getAllChildren(id, fileId);
|
||||||
|
|
||||||
|
@ -129,24 +128,24 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDownloadCheckboxExists = function(id) {
|
const isDownloadCheckboxExists = function(id) {
|
||||||
return ($('cbPrio' + id) !== null);
|
return ($("cbPrio" + id) !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createDownloadCheckbox = function(id, fileId, checked) {
|
const createDownloadCheckbox = function(id, fileId, checked) {
|
||||||
const checkbox = new Element('input');
|
const checkbox = new Element("input");
|
||||||
checkbox.set('type', 'checkbox');
|
checkbox.set("type", "checkbox");
|
||||||
checkbox.set('id', 'cbPrio' + id);
|
checkbox.set("id", "cbPrio" + id);
|
||||||
checkbox.set('data-id', id);
|
checkbox.set("data-id", id);
|
||||||
checkbox.set('data-file-id', fileId);
|
checkbox.set("data-file-id", fileId);
|
||||||
checkbox.set('class', 'DownloadedCB');
|
checkbox.set("class", "DownloadedCB");
|
||||||
checkbox.addEvent('click', fileCheckboxClicked);
|
checkbox.addEvent("click", fileCheckboxClicked);
|
||||||
|
|
||||||
updateCheckbox(checkbox, checked);
|
updateCheckbox(checkbox, checked);
|
||||||
return checkbox;
|
return checkbox;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDownloadCheckbox = function(id, checked) {
|
const updateDownloadCheckbox = function(id, checked) {
|
||||||
const checkbox = $('cbPrio' + id);
|
const checkbox = $("cbPrio" + id);
|
||||||
updateCheckbox(checkbox, checked);
|
updateCheckbox(checkbox, checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,43 +164,42 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPriorityComboExists = function(id) {
|
const isPriorityComboExists = function(id) {
|
||||||
return ($('comboPrio' + id) !== null);
|
return ($("comboPrio" + id) !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPriorityOptionElement = function(priority, selected, html) {
|
const createPriorityOptionElement = function(priority, selected, html) {
|
||||||
const elem = new Element('option');
|
const elem = new Element("option");
|
||||||
elem.set('value', priority.toString());
|
elem.set("value", priority.toString());
|
||||||
elem.set('html', html);
|
elem.set("html", html);
|
||||||
if (selected)
|
if (selected)
|
||||||
elem.selected = true;
|
elem.selected = true;
|
||||||
return elem;
|
return elem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPriorityCombo = function(id, fileId, selectedPriority) {
|
const createPriorityCombo = function(id, fileId, selectedPriority) {
|
||||||
const select = new Element('select');
|
const select = new Element("select");
|
||||||
select.set('id', 'comboPrio' + id);
|
select.set("id", "comboPrio" + id);
|
||||||
select.set('data-id', id);
|
select.set("data-id", id);
|
||||||
select.set('data-file-id', fileId);
|
select.set("data-file-id", fileId);
|
||||||
select.addClass('combo_priority');
|
select.addClass("combo_priority");
|
||||||
select.addEvent('change', fileComboboxChanged);
|
select.addEvent("change", fileComboboxChanged);
|
||||||
|
|
||||||
createPriorityOptionElement(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), 'QBT_TR(Do not download)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.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.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.Maximum, (FilePriority.Maximum === selectedPriority), "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select);
|
||||||
|
|
||||||
// "Mixed" priority is for display only; it shouldn't be selectable
|
// "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]');
|
const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), "QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]");
|
||||||
mixedPriorityOption.set('disabled', true);
|
mixedPriorityOption.set("disabled", true);
|
||||||
mixedPriorityOption.injectInside(select);
|
mixedPriorityOption.injectInside(select);
|
||||||
|
|
||||||
return select;
|
return select;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePriorityCombo = function(id, selectedPriority) {
|
const updatePriorityCombo = function(id, selectedPriority) {
|
||||||
const combobox = $('comboPrio' + id);
|
const combobox = $("comboPrio" + id);
|
||||||
|
if (parseInt(combobox.value, 10) !== selectedPriority)
|
||||||
if (parseInt(combobox.value) !== selectedPriority)
|
|
||||||
selectComboboxPriority(combobox, selectedPriority);
|
selectComboboxPriority(combobox, selectedPriority);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,7 +207,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const options = combobox.options;
|
const options = combobox.options;
|
||||||
for (let i = 0; i < options.length; ++i) {
|
for (let i = 0; i < options.length; ++i) {
|
||||||
const option = options[i];
|
const option = options[i];
|
||||||
if (parseInt(option.value) === priority)
|
if (parseInt(option.value, 10) === priority)
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
else
|
else
|
||||||
option.selected = false;
|
option.selected = false;
|
||||||
|
@ -224,12 +222,12 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const rowIds = [];
|
const rowIds = [];
|
||||||
const fileIds = [];
|
const fileIds = [];
|
||||||
let priority = FilePriority.Ignored;
|
let priority = FilePriority.Ignored;
|
||||||
const checkbox = $('tristate_cb');
|
const checkbox = $("tristate_cb");
|
||||||
|
|
||||||
if (checkbox.state === "checked") {
|
if (checkbox.state === "checked") {
|
||||||
setCheckboxUnchecked(checkbox);
|
setCheckboxUnchecked(checkbox);
|
||||||
// set file priority for all checked to Ignored
|
// set file priority for all checked to Ignored
|
||||||
torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) {
|
torrentFilesTable.getFilteredAndSortedRows().forEach((row) => {
|
||||||
const rowId = row.rowId;
|
const rowId = row.rowId;
|
||||||
const fileId = row.full_data.fileId;
|
const fileId = row.full_data.fileId;
|
||||||
const isChecked = (row.full_data.checked === TriState.Checked);
|
const isChecked = (row.full_data.checked === TriState.Checked);
|
||||||
|
@ -244,7 +242,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
setCheckboxChecked(checkbox);
|
setCheckboxChecked(checkbox);
|
||||||
priority = FilePriority.Normal;
|
priority = FilePriority.Normal;
|
||||||
// set file priority for all unchecked to Normal
|
// set file priority for all unchecked to Normal
|
||||||
torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) {
|
torrentFilesTable.getFilteredAndSortedRows().forEach((row) => {
|
||||||
const rowId = row.rowId;
|
const rowId = row.rowId;
|
||||||
const fileId = row.full_data.fileId;
|
const fileId = row.full_data.fileId;
|
||||||
const isUnchecked = (row.full_data.checked === TriState.Unchecked);
|
const isUnchecked = (row.full_data.checked === TriState.Unchecked);
|
||||||
|
@ -261,7 +259,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateGlobalCheckbox = function() {
|
const updateGlobalCheckbox = function() {
|
||||||
const checkbox = $('tristate_cb');
|
const checkbox = $("tristate_cb");
|
||||||
if (isAllCheckboxesChecked())
|
if (isAllCheckboxesChecked())
|
||||||
setCheckboxChecked(checkbox);
|
setCheckboxChecked(checkbox);
|
||||||
else if (isAllCheckboxesUnchecked())
|
else if (isAllCheckboxesUnchecked())
|
||||||
|
@ -288,7 +286,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllCheckboxesChecked = function() {
|
const isAllCheckboxesChecked = function() {
|
||||||
const checkboxes = $$('input.DownloadedCB');
|
const checkboxes = $$("input.DownloadedCB");
|
||||||
for (let i = 0; i < checkboxes.length; ++i) {
|
for (let i = 0; i < checkboxes.length; ++i) {
|
||||||
if (!checkboxes[i].checked)
|
if (!checkboxes[i].checked)
|
||||||
return false;
|
return false;
|
||||||
|
@ -297,7 +295,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllCheckboxesUnchecked = function() {
|
const isAllCheckboxesUnchecked = function() {
|
||||||
const checkboxes = $$('input.DownloadedCB');
|
const checkboxes = $$("input.DownloadedCB");
|
||||||
for (let i = 0; i < checkboxes.length; ++i) {
|
for (let i = 0; i < checkboxes.length; ++i) {
|
||||||
if (checkboxes[i].checked)
|
if (checkboxes[i].checked)
|
||||||
return false;
|
return false;
|
||||||
|
@ -311,12 +309,12 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
clearTimeout(loadTorrentFilesDataTimer);
|
clearTimeout(loadTorrentFilesDataTimer);
|
||||||
new Request({
|
new Request({
|
||||||
url: 'api/v2/torrents/filePrio',
|
url: "api/v2/torrents/filePrio",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data: {
|
data: {
|
||||||
'hash': current_hash,
|
"hash": current_hash,
|
||||||
'id': fileIds.join('|'),
|
"id": fileIds.join("|"),
|
||||||
'priority': priority
|
"priority": priority
|
||||||
},
|
},
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000);
|
loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000);
|
||||||
|
@ -324,10 +322,10 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
}).send();
|
}).send();
|
||||||
|
|
||||||
const ignore = (priority === FilePriority.Ignored);
|
const ignore = (priority === FilePriority.Ignored);
|
||||||
ids.forEach(function(_id) {
|
ids.forEach((_id) => {
|
||||||
torrentFilesTable.setIgnored(_id, ignore);
|
torrentFilesTable.setIgnored(_id, ignore);
|
||||||
|
|
||||||
const combobox = $('comboPrio' + _id);
|
const combobox = $("comboPrio" + _id);
|
||||||
if (combobox !== null)
|
if (combobox !== null)
|
||||||
selectComboboxPriority(combobox, priority);
|
selectComboboxPriority(combobox, priority);
|
||||||
});
|
});
|
||||||
|
@ -337,8 +335,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
let loadTorrentFilesDataTimer;
|
let loadTorrentFilesDataTimer;
|
||||||
const loadTorrentFilesData = function() {
|
const loadTorrentFilesData = function() {
|
||||||
if ($('prop_files').hasClass('invisible')
|
if ($("prop_files").hasClass("invisible")
|
||||||
|| $('propertiesPanel_collapseToggle').hasClass('panel-expand')) {
|
|| $("propertiesPanel_collapseToggle").hasClass("panel-expand")) {
|
||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -355,10 +353,10 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
current_hash = new_hash;
|
current_hash = new_hash;
|
||||||
loadedNewTorrent = true;
|
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({
|
new Request.JSON({
|
||||||
url: url,
|
url: url,
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onComplete: function() {
|
onComplete: function() {
|
||||||
clearTimeout(loadTorrentFilesDataTimer);
|
clearTimeout(loadTorrentFilesDataTimer);
|
||||||
|
@ -388,7 +386,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const handleNewTorrentFiles = function(files) {
|
const handleNewTorrentFiles = function(files) {
|
||||||
is_seed = (files.length > 0) ? files[0].is_seed : true;
|
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);
|
let progress = (file.progress * 100).round(1);
|
||||||
if ((progress === 100) && (file.progress < 1))
|
if ((progress === 100) && (file.progress < 1))
|
||||||
progress = 99.9;
|
progress = 99.9;
|
||||||
|
@ -421,13 +419,13 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
const rootNode = new window.qBittorrent.FileTree.FolderNode();
|
||||||
|
|
||||||
rows.forEach(function(row) {
|
rows.forEach((row) => {
|
||||||
const pathItems = row.fileName.split(window.qBittorrent.Filesystem.PathSeparator);
|
const pathItems = row.fileName.split(window.qBittorrent.Filesystem.PathSeparator);
|
||||||
|
|
||||||
pathItems.pop(); // remove last item (i.e. file name)
|
pathItems.pop(); // remove last item (i.e. file name)
|
||||||
let parent = rootNode;
|
let parent = rootNode;
|
||||||
pathItems.forEach(function(folderName) {
|
pathItems.forEach((folderName) => {
|
||||||
if (folderName === '.unwanted')
|
if (folderName === ".unwanted")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let folderNode = null;
|
let folderNode = null;
|
||||||
|
@ -474,7 +472,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
parent.addChild(childNode);
|
parent.addChild(childNode);
|
||||||
|
|
||||||
++rowId;
|
++rowId;
|
||||||
}.bind(this));
|
});
|
||||||
|
|
||||||
torrentFilesTable.populateTable(rootNode);
|
torrentFilesTable.populateTable(rootNode);
|
||||||
torrentFilesTable.updateTable(false);
|
torrentFilesTable.updateTable(false);
|
||||||
|
@ -497,16 +495,14 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const expandFolder = function(id) {
|
const expandFolder = function(id) {
|
||||||
const node = torrentFilesTable.getNode(id);
|
const node = torrentFilesTable.getNode(id);
|
||||||
if (node.isFolder) {
|
if (node.isFolder)
|
||||||
expandNode(node);
|
expandNode(node);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const collapseFolder = function(id) {
|
const collapseFolder = function(id) {
|
||||||
const node = torrentFilesTable.getNode(id);
|
const node = torrentFilesTable.getNode(id);
|
||||||
if (node.isFolder) {
|
if (node.isFolder)
|
||||||
collapseNode(node);
|
collapseNode(node);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const filesPriorityMenuClicked = function(priority) {
|
const filesPriorityMenuClicked = function(priority) {
|
||||||
|
@ -516,8 +512,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const rowIds = [];
|
const rowIds = [];
|
||||||
const fileIds = [];
|
const fileIds = [];
|
||||||
selectedRows.forEach(function(rowId) {
|
selectedRows.forEach((rowId) => {
|
||||||
const elem = $('comboPrio' + rowId);
|
const elem = $("comboPrio" + rowId);
|
||||||
rowIds.push(rowId);
|
rowIds.push(rowId);
|
||||||
fileIds.push(elem.get("data-file-id"));
|
fileIds.push(elem.get("data-file-id"));
|
||||||
});
|
});
|
||||||
|
@ -526,10 +522,10 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const uniqueFileIds = {};
|
const uniqueFileIds = {};
|
||||||
for (let i = 0; i < rowIds.length; ++i) {
|
for (let i = 0; i < rowIds.length; ++i) {
|
||||||
const rows = getAllChildren(rowIds[i], fileIds[i]);
|
const rows = getAllChildren(rowIds[i], fileIds[i]);
|
||||||
rows.rowIds.forEach(function(rowId) {
|
rows.rowIds.forEach((rowId) => {
|
||||||
uniqueRowIds[rowId] = true;
|
uniqueRowIds[rowId] = true;
|
||||||
});
|
});
|
||||||
rows.fileIds.forEach(function(fileId) {
|
rows.fileIds.forEach((fileId) => {
|
||||||
uniqueFileIds[fileId] = true;
|
uniqueFileIds[fileId] = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -549,11 +545,11 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
const path = node.path;
|
const path = node.path;
|
||||||
|
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'renamePage',
|
id: "renamePage",
|
||||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
||||||
loadMethod: 'iframe',
|
loadMethod: "iframe",
|
||||||
contentURL: 'rename_file.html?hash=' + hash + '&isFolder=' + node.isFolder
|
contentURL: "rename_file.html?hash=" + hash + "&isFolder=" + node.isFolder
|
||||||
+ '&path=' + encodeURIComponent(path),
|
+ "&path=" + encodeURIComponent(path),
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -566,11 +562,11 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const multiFileRename = function(hash) {
|
const multiFileRename = function(hash) {
|
||||||
new MochaUI.Window({
|
new MochaUI.Window({
|
||||||
id: 'multiRenamePage',
|
id: "multiRenamePage",
|
||||||
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TorrentContentTreeView]",
|
||||||
data: { hash: hash, selectedRows: torrentFilesTable.selectedRows },
|
data: { hash: hash, selectedRows: torrentFilesTable.selectedRows },
|
||||||
loadMethod: 'xhr',
|
loadMethod: "xhr",
|
||||||
contentURL: 'rename_files.html',
|
contentURL: "rename_files.html",
|
||||||
scrollbars: false,
|
scrollbars: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
|
@ -578,25 +574,23 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 420,
|
height: 420,
|
||||||
resizeLimit: { 'x': [800], 'y': [420] }
|
resizeLimit: { "x": [800], "y": [420] }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
|
||||||
targets: '#torrentFilesTableDiv tr',
|
targets: "#torrentFilesTableDiv tr",
|
||||||
menu: 'torrentFilesMenu',
|
menu: "torrentFilesMenu",
|
||||||
actions: {
|
actions: {
|
||||||
Rename: function(element, ref) {
|
Rename: function(element, ref) {
|
||||||
const hash = torrentsTable.getCurrentTorrentID();
|
const hash = torrentsTable.getCurrentTorrentID();
|
||||||
if (!hash)
|
if (!hash)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (torrentFilesTable.selectedRowsIds().length > 1) {
|
if (torrentFilesTable.selectedRowsIds().length > 1)
|
||||||
multiFileRename(hash);
|
multiFileRename(hash);
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
singleFileRename(hash);
|
singleFileRename(hash);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
FilePrioIgnore: function(element, ref) {
|
FilePrioIgnore: function(element, ref) {
|
||||||
|
@ -618,20 +612,20 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
if (is_seed)
|
if (is_seed)
|
||||||
this.hideItem('FilePrio');
|
this.hideItem("FilePrio");
|
||||||
else
|
else
|
||||||
this.showItem('FilePrio');
|
this.showItem("FilePrio");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentFilesTable.setup('torrentFilesTableDiv', 'torrentFilesTableFixedHeaderDiv', torrentFilesContextMenu);
|
torrentFilesTable.setup("torrentFilesTableDiv", "torrentFilesTableFixedHeaderDiv", torrentFilesContextMenu);
|
||||||
// inject checkbox into table header
|
// inject checkbox into table header
|
||||||
const tableHeaders = $$('#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th');
|
const tableHeaders = $$("#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||||
if (tableHeaders.length > 0) {
|
if (tableHeaders.length > 0) {
|
||||||
const checkbox = new Element('input');
|
const checkbox = new Element("input");
|
||||||
checkbox.set('type', 'checkbox');
|
checkbox.set("type", "checkbox");
|
||||||
checkbox.set('id', 'tristate_cb');
|
checkbox.set("id", "tristate_cb");
|
||||||
checkbox.addEvent('click', switchCheckboxState);
|
checkbox.addEvent("click", switchCheckboxState);
|
||||||
|
|
||||||
const checkboxTH = tableHeaders[0];
|
const checkboxTH = tableHeaders[0];
|
||||||
checkbox.injectInside(checkboxTH);
|
checkbox.injectInside(checkboxTH);
|
||||||
|
@ -639,14 +633,14 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
// default sort by name column
|
// default sort by name column
|
||||||
if (torrentFilesTable.getSortedColumn() === null)
|
if (torrentFilesTable.getSortedColumn() === null)
|
||||||
torrentFilesTable.setSortedColumn('name');
|
torrentFilesTable.setSortedColumn("name");
|
||||||
|
|
||||||
// listen for changes to torrentFilesFilterInput
|
// listen for changes to torrentFilesFilterInput
|
||||||
let torrentFilesFilterInputTimer = -1;
|
let torrentFilesFilterInputTimer = -1;
|
||||||
$('torrentFilesFilterInput').addEvent('input', () => {
|
$("torrentFilesFilterInput").addEvent("input", () => {
|
||||||
clearTimeout(torrentFilesFilterInputTimer);
|
clearTimeout(torrentFilesFilterInputTimer);
|
||||||
|
|
||||||
const value = $('torrentFilesFilterInput').get("value");
|
const value = $("torrentFilesFilterInput").get("value");
|
||||||
torrentFilesTable.setFilter(value);
|
torrentFilesTable.setFilter(value);
|
||||||
|
|
||||||
torrentFilesFilterInputTimer = setTimeout(() => {
|
torrentFilesFilterInputTimer = setTimeout(() => {
|
||||||
|
@ -668,7 +662,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
* Show/hide a node's row
|
* Show/hide a node's row
|
||||||
*/
|
*/
|
||||||
const _hideNode = function(node, shouldHide) {
|
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
|
// span won't exist if row has been filtered out
|
||||||
if (span === null)
|
if (span === null)
|
||||||
return;
|
return;
|
||||||
|
@ -683,7 +677,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
* Update a node's collapsed state and icon
|
* Update a node's collapsed state and icon
|
||||||
*/
|
*/
|
||||||
const _updateNodeState = function(node, isCollapsed) {
|
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
|
// span won't exist if row has been filtered out
|
||||||
if (span === null)
|
if (span === null)
|
||||||
return;
|
return;
|
||||||
|
@ -701,7 +695,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _isCollapsed = function(node) {
|
const _isCollapsed = function(node) {
|
||||||
const span = $('filesTablefileName' + node.rowId);
|
const span = $("filesTablefileName" + node.rowId);
|
||||||
if (span === null)
|
if (span === null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -721,8 +715,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const expandAllNodes = function() {
|
const expandAllNodes = function() {
|
||||||
const root = torrentFilesTable.getRoot();
|
const root = torrentFilesTable.getRoot();
|
||||||
root.children.each(function(node) {
|
root.children.each((node) => {
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
_collapseNode(child, false, true, false);
|
_collapseNode(child, false, true, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -731,8 +725,8 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
|
|
||||||
const collapseAllNodes = function() {
|
const collapseAllNodes = function() {
|
||||||
const root = torrentFilesTable.getRoot();
|
const root = torrentFilesTable.getRoot();
|
||||||
root.children.each(function(node) {
|
root.children.each((node) => {
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
_collapseNode(child, true, true, false);
|
_collapseNode(child, true, true, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -757,7 +751,7 @@ window.qBittorrent.PropFiles = (function() {
|
||||||
if (!isChildNode || applyToChildren || !canSkipNode)
|
if (!isChildNode || applyToChildren || !canSkipNode)
|
||||||
_updateNodeState(node, shouldCollapse);
|
_updateNodeState(node, shouldCollapse);
|
||||||
|
|
||||||
node.children.each(function(child) {
|
node.children.each((child) => {
|
||||||
_hideNode(child, shouldCollapse);
|
_hideNode(child, shouldCollapse);
|
||||||
|
|
||||||
if (!child.isFolder)
|
if (!child.isFolder)
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
if (window.qBittorrent === undefined) {
|
if (window.qBittorrent === undefined)
|
||||||
window.qBittorrent = {};
|
window.qBittorrent = {};
|
||||||
}
|
|
||||||
|
|
||||||
window.qBittorrent.PropGeneral = (function() {
|
window.qBittorrent.PropGeneral = (function() {
|
||||||
const exports = function() {
|
const exports = function() {
|
||||||
|
@ -42,42 +41,43 @@ window.qBittorrent.PropGeneral = (function() {
|
||||||
const piecesBar = new window.qBittorrent.PiecesBar.PiecesBar([], {
|
const piecesBar = new window.qBittorrent.PiecesBar.PiecesBar([], {
|
||||||
height: 16
|
height: 16
|
||||||
});
|
});
|
||||||
$('progress').appendChild(piecesBar);
|
$("progress").appendChild(piecesBar);
|
||||||
|
|
||||||
const clearData = function() {
|
const clearData = function() {
|
||||||
$('time_elapsed').set('html', '');
|
$("time_elapsed").set("html", "");
|
||||||
$('eta').set('html', '');
|
$("eta").set("html", "");
|
||||||
$('nb_connections').set('html', '');
|
$("nb_connections").set("html", "");
|
||||||
$('total_downloaded').set('html', '');
|
$("total_downloaded").set("html", "");
|
||||||
$('total_uploaded').set('html', '');
|
$("total_uploaded").set("html", "");
|
||||||
$('dl_speed').set('html', '');
|
$("dl_speed").set("html", "");
|
||||||
$('up_speed').set('html', '');
|
$("up_speed").set("html", "");
|
||||||
$('dl_limit').set('html', '');
|
$("dl_limit").set("html", "");
|
||||||
$('up_limit').set('html', '');
|
$("up_limit").set("html", "");
|
||||||
$('total_wasted').set('html', '');
|
$("total_wasted").set("html", "");
|
||||||
$('seeds').set('html', '');
|
$("seeds").set("html", "");
|
||||||
$('peers').set('html', '');
|
$("peers").set("html", "");
|
||||||
$('share_ratio').set('html', '');
|
$("share_ratio").set("html", "");
|
||||||
$('popularity').set('html', '');
|
$("popularity").set("html", "");
|
||||||
$('reannounce').set('html', '');
|
$("reannounce").set("html", "");
|
||||||
$('last_seen').set('html', '');
|
$("last_seen").set("html", "");
|
||||||
$('total_size').set('html', '');
|
$("total_size").set("html", "");
|
||||||
$('pieces').set('html', '');
|
$("pieces").set("html", "");
|
||||||
$('created_by').set('html', '');
|
$("created_by").set("html", "");
|
||||||
$('addition_date').set('html', '');
|
$("addition_date").set("html", "");
|
||||||
$('completion_date').set('html', '');
|
$("completion_date").set("html", "");
|
||||||
$('creation_date').set('html', '');
|
$("creation_date").set("html", "");
|
||||||
$('torrent_hash_v1').set('html', '');
|
$("torrent_hash_v1").set("html", "");
|
||||||
$('torrent_hash_v2').set('html', '');
|
$("torrent_hash_v2").set("html", "");
|
||||||
$('save_path').set('html', '');
|
$("save_path").set("html", "");
|
||||||
$('comment').set('html', '');
|
$("comment").set("html", "");
|
||||||
|
$("private").set("html", "");
|
||||||
piecesBar.clear();
|
piecesBar.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
let loadTorrentDataTimer;
|
let loadTorrentDataTimer;
|
||||||
const loadTorrentData = function() {
|
const loadTorrentData = function() {
|
||||||
if ($('prop_general').hasClass('invisible')
|
if ($("prop_general").hasClass("invisible")
|
||||||
|| $('propertiesPanel_collapseToggle').hasClass('panel-expand')) {
|
|| $("propertiesPanel_collapseToggle").hasClass("panel-expand")) {
|
||||||
// Tab changed, don't do anything
|
// Tab changed, don't do anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -88,18 +88,18 @@ window.qBittorrent.PropGeneral = (function() {
|
||||||
loadTorrentDataTimer = loadTorrentData.delay(5000);
|
loadTorrentDataTimer = loadTorrentData.delay(5000);
|
||||||
return;
|
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({
|
new Request.JSON({
|
||||||
url: url,
|
url: url,
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onFailure: function() {
|
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);
|
clearTimeout(loadTorrentDataTimer);
|
||||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||||
},
|
},
|
||||||
onSuccess: function(data) {
|
onSuccess: function(data) {
|
||||||
$('error_div').set('html', '');
|
$("error_div").set("html", "");
|
||||||
if (data) {
|
if (data) {
|
||||||
// Update Torrent data
|
// Update Torrent data
|
||||||
|
|
||||||
|
@ -108,109 +108,118 @@ window.qBittorrent.PropGeneral = (function() {
|
||||||
.replace("%1", window.qBittorrent.Misc.friendlyDuration(data.time_elapsed))
|
.replace("%1", window.qBittorrent.Misc.friendlyDuration(data.time_elapsed))
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyDuration(data.seeding_time))
|
.replace("%2", window.qBittorrent.Misc.friendlyDuration(data.seeding_time))
|
||||||
: window.qBittorrent.Misc.friendlyDuration(data.time_elapsed);
|
: 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]"
|
const nbConnections = "QBT_TR(%1 (%2 max))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", data.nb_connections)
|
.replace("%1", data.nb_connections)
|
||||||
.replace("%2", ((data.nb_connections_limit < 0) ? "∞" : data.nb_connections_limit));
|
.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]"
|
const totalDownloaded = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded))
|
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded))
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded_session));
|
.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]"
|
const totalUploaded = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded))
|
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded))
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded_session));
|
.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]"
|
const dlSpeed = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.dl_speed, true))
|
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.dl_speed, true))
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.dl_speed_avg, 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]"
|
const upSpeed = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.up_speed, true))
|
.replace("%1", window.qBittorrent.Misc.friendlyUnit(data.up_speed, true))
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.up_speed_avg, 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)
|
const dlLimit = (data.dl_limit === -1)
|
||||||
? "∞"
|
? "∞"
|
||||||
: window.qBittorrent.Misc.friendlyUnit(data.dl_limit, true);
|
: window.qBittorrent.Misc.friendlyUnit(data.dl_limit, true);
|
||||||
$('dl_limit').set('html', dlLimit);
|
$("dl_limit").set("html", dlLimit);
|
||||||
|
|
||||||
const upLimit = (data.up_limit === -1)
|
const upLimit = (data.up_limit === -1)
|
||||||
? "∞"
|
? "∞"
|
||||||
: window.qBittorrent.Misc.friendlyUnit(data.up_limit, true);
|
: 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]"
|
const seeds = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", data.seeds)
|
.replace("%1", data.seeds)
|
||||||
.replace("%2", data.seeds_total);
|
.replace("%2", data.seeds_total);
|
||||||
$('seeds').set('html', seeds);
|
$("seeds").set("html", seeds);
|
||||||
|
|
||||||
const peers = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
const peers = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", data.peers)
|
.replace("%1", data.peers)
|
||||||
.replace("%2", data.peers_total);
|
.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)
|
const lastSeen = (data.last_seen >= 0)
|
||||||
? new Date(data.last_seen * 1000).toLocaleString()
|
? new Date(data.last_seen * 1000).toLocaleString()
|
||||||
: "QBT_TR(Never)QBT_TR[CONTEXT=PropertiesWidget]";
|
: "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) : "";
|
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)
|
const pieces = (data.pieces_num >= 0)
|
||||||
? "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]"
|
? "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]"
|
||||||
.replace("%1", data.pieces_num)
|
.replace("%1", data.pieces_num)
|
||||||
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.piece_size))
|
.replace("%2", window.qBittorrent.Misc.friendlyUnit(data.piece_size))
|
||||||
.replace("%3", data.pieces_have)
|
.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)
|
const additionDate = (data.addition_date >= 0)
|
||||||
? new Date(data.addition_date * 1000).toLocaleString()
|
? new Date(data.addition_date * 1000).toLocaleString()
|
||||||
: "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
|
: "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
|
||||||
$('addition_date').set('html', additionDate);
|
$("addition_date").set("html", additionDate);
|
||||||
|
|
||||||
const completionDate = (data.completion_date >= 0)
|
const completionDate = (data.completion_date >= 0)
|
||||||
? new Date(data.completion_date * 1000).toLocaleString()
|
? new Date(data.completion_date * 1000).toLocaleString()
|
||||||
: "";
|
: "";
|
||||||
$('completion_date').set('html', completionDate);
|
$("completion_date").set("html", completionDate);
|
||||||
|
|
||||||
const creationDate = (data.creation_date >= 0)
|
const creationDate = (data.creation_date >= 0)
|
||||||
? new Date(data.creation_date * 1000).toLocaleString()
|
? new Date(data.creation_date * 1000).toLocaleString()
|
||||||
: "";
|
: "";
|
||||||
$('creation_date').set('html', creationDate);
|
$("creation_date").set("html", creationDate);
|
||||||
|
|
||||||
const torrentHashV1 = (data.infohash_v1 !== "")
|
const torrentHashV1 = (data.infohash_v1 !== "")
|
||||||
? data.infohash_v1
|
? data.infohash_v1
|
||||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
: "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 !== "")
|
const torrentHashV2 = (data.infohash_v2 !== "")
|
||||||
? data.infohash_v2
|
? data.infohash_v2
|
||||||
: "QBT_TR(N/A)QBT_TR[CONTEXT=PropertiesWidget]";
|
: "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 {
|
else {
|
||||||
clearData();
|
clearData();
|
||||||
|
@ -220,25 +229,24 @@ window.qBittorrent.PropGeneral = (function() {
|
||||||
}
|
}
|
||||||
}).send();
|
}).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({
|
new Request.JSON({
|
||||||
url: piecesUrl,
|
url: piecesUrl,
|
||||||
method: 'get',
|
method: "get",
|
||||||
noCache: true,
|
noCache: true,
|
||||||
onFailure: function() {
|
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);
|
clearTimeout(loadTorrentDataTimer);
|
||||||
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
loadTorrentDataTimer = loadTorrentData.delay(10000);
|
||||||
},
|
},
|
||||||
onSuccess: function(data) {
|
onSuccess: function(data) {
|
||||||
$('error_div').set('html', '');
|
$("error_div").set("html", "");
|
||||||
|
|
||||||
if (data) {
|
if (data)
|
||||||
piecesBar.setPieces(data);
|
piecesBar.setPieces(data);
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
clearData();
|
clearData();
|
||||||
}
|
|
||||||
clearTimeout(loadTorrentDataTimer);
|
clearTimeout(loadTorrentDataTimer);
|
||||||
loadTorrentDataTimer = loadTorrentData.delay(5000);
|
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