Compare commits
94 Commits
5edaf2cf10
...
v5_1_x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33e5e77220 | ||
|
|
59b9505e60 | ||
|
|
2f4c19e7cd | ||
|
|
e6a99e812e | ||
|
|
6ede4e49ff | ||
|
|
7d7bfd818d | ||
|
|
6038d6261a | ||
|
|
172089cf4f | ||
|
|
1ab24a22d0 | ||
|
|
f2c1ce585a | ||
|
|
f76b17155e | ||
|
|
7bbe8eff51 | ||
|
|
2979b1e0e4 | ||
|
|
4ffb5af6aa | ||
|
|
ba7c7e283e | ||
|
|
571094cc9c | ||
|
|
8f1fc451ae | ||
|
|
0f7a27ea03 | ||
|
|
015950ea61 | ||
|
|
772ba5f6bc | ||
|
|
298bd20299 | ||
|
|
e6f50147d9 | ||
|
|
18fb9936f0 | ||
|
|
3fa812ced6 | ||
|
|
a76f12f3db | ||
|
|
d76712256c | ||
|
|
f3e47facef | ||
|
|
202ff8a099 | ||
|
|
c0585441fb | ||
|
|
a8b6cbceb0 | ||
|
|
6ad073e0bc | ||
|
|
ad68813fe8 | ||
|
|
22df0b45c5 | ||
|
|
bb34444ddc | ||
|
|
dd5c934103 | ||
|
|
3fca180e98 | ||
|
|
9b29d37d21 | ||
|
|
206d5abf84 | ||
|
|
101f35dcf2 | ||
|
|
13282d94ef | ||
|
|
1daa42e4fe | ||
|
|
ea9f3800ce | ||
|
|
af14584772 | ||
|
|
7d51524251 | ||
|
|
7a9aac79f9 | ||
|
|
085ae0d1c4 | ||
|
|
f748a682ca | ||
|
|
df987cc954 | ||
|
|
535fc42747 | ||
|
|
1da31bc2e1 | ||
|
|
9515ca59f2 | ||
|
|
eaf9017aa4 | ||
|
|
f51ad39ad9 | ||
|
|
9133b16431 | ||
|
|
909a3eb44e | ||
|
|
778aa64c54 | ||
|
|
7049f80a01 | ||
|
|
87b90b7fd7 | ||
|
|
b3690494ab | ||
|
|
f4e6b515c2 | ||
|
|
a721540e6c | ||
|
|
3fd05d001f | ||
|
|
f04b114b64 | ||
|
|
da87be2b12 | ||
|
|
891265b390 | ||
|
|
f46e44d3ed | ||
|
|
a4094a440d | ||
|
|
46c3da21e1 | ||
|
|
2f06ea2587 | ||
|
|
cfbf6b73ff | ||
|
|
c687a7d0d3 | ||
|
|
009cc71f9b | ||
|
|
de1cf208ce | ||
|
|
5f49472fa4 | ||
|
|
2076302170 | ||
|
|
2a33e187eb | ||
|
|
00149e03c0 | ||
|
|
57d529c17a | ||
|
|
d492fcf29a | ||
|
|
d0caa35b39 | ||
|
|
ec7a00af92 | ||
|
|
76a3aba7e0 | ||
|
|
7003ac3f4d | ||
|
|
964be0fa1c | ||
|
|
c1defceccf | ||
|
|
260394623d | ||
|
|
478c2d5b12 | ||
|
|
49cfbd9a49 | ||
|
|
d028f46fab | ||
|
|
57b24a200e | ||
|
|
269dfe87e0 | ||
|
|
6a1c465d85 | ||
|
|
bc7d5c1f8f | ||
|
|
8aabef423c |
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
||||
custom: "https://www.qbittorrent.org/donate"
|
||||
custom: "https://www.qbittorrent.org/donate.php"
|
||||
|
||||
14
.github/workflows/ci_file_health.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
curl \
|
||||
-L \
|
||||
-o "${{ runner.temp }}/pandoc.tar.gz" \
|
||||
"https://github.com/jgm/pandoc/releases/download/3.7.0.2/pandoc-3.7.0.2-linux-amd64.tar.gz"
|
||||
"https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-linux-amd64.tar.gz"
|
||||
tar -xf "${{ runner.temp }}/pandoc.tar.gz" -C "${{ github.workspace }}/.."
|
||||
mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}"
|
||||
# run pandoc
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
pip install zizmor
|
||||
IGNORE_RULEID='(.ruleId != "zizmor/template-injection")
|
||||
and (.ruleId != "zizmor/unpinned-uses")'
|
||||
IGNORE_ID='(.id != "zizmor/template-injection")
|
||||
and (.id != "zizmor/unpinned-uses")'
|
||||
IGNORE_RULEID='(.ruleId != "template-injection")
|
||||
and (.ruleId != "unpinned-uses")'
|
||||
IGNORE_ID='(.id != "template-injection")
|
||||
and (.id != "unpinned-uses")'
|
||||
zizmor \
|
||||
--format sarif \
|
||||
--persona auditor \
|
||||
--pedantic \
|
||||
./ \
|
||||
| jq "(.runs[].results |= map(select($IGNORE_RULEID)))
|
||||
| (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \
|
||||
|
||||
30
.github/workflows/ci_macos.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.9.1"]
|
||||
qt_version: ["6.7.0"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -43,8 +43,8 @@ jobs:
|
||||
command: |
|
||||
brew update > /dev/null
|
||||
brew install \
|
||||
cmake ninja \
|
||||
openssl@3 zlib
|
||||
# preinstalled on the image: cmake ninja
|
||||
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=1G
|
||||
max_size=2G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
@@ -70,9 +70,6 @@ jobs:
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -98,7 +95,7 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
@@ -112,7 +109,7 @@ jobs:
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
@@ -122,24 +119,17 @@ jobs:
|
||||
|
||||
- name: Prepare build artifacts
|
||||
run: |
|
||||
# create .dmg
|
||||
appName="qbittorrent"
|
||||
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
|
||||
appName="qbittorrent-nox"
|
||||
fi
|
||||
# package
|
||||
pushd build
|
||||
# packaging
|
||||
macdeployqt "$appName.app" -no-strip
|
||||
# code signing
|
||||
xattr -cr "$appName.app"
|
||||
codesign --force --sign - \
|
||||
"$appName.app" \
|
||||
"$appName.app/Contents/Frameworks"/* \
|
||||
"$appName.app/Contents/MacOS/$appName"
|
||||
codesign --verify --deep --strict -v "$appName.app"
|
||||
# create .dmg
|
||||
PACKAGE_RETRY=0
|
||||
while [ "$PACKAGE_RETRY" -lt "3" ]; do
|
||||
if hdiutil create -fs HFS+ -srcfolder "$appName.app" -volname "$appName" "$appName.dmg"; then
|
||||
macdeployqt "$appName.app" -dmg -no-strip
|
||||
if [ -f "$appName.dmg" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
|
||||
19
.github/workflows/ci_python.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
python-version: '3' # use default version
|
||||
|
||||
- name: Install tools (auxiliary scripts)
|
||||
run: pip install bandit isort pycodestyle pyflakes
|
||||
run: pip install bandit pycodestyle pyflakes
|
||||
|
||||
- name: Gather files (auxiliary scripts)
|
||||
run: |
|
||||
@@ -44,10 +44,6 @@ jobs:
|
||||
--max-line-length=1000 \
|
||||
--statistics \
|
||||
$PY_FILES
|
||||
isort \
|
||||
--check \
|
||||
--diff \
|
||||
$PY_FILES
|
||||
|
||||
- name: Build code (auxiliary scripts)
|
||||
run: |
|
||||
@@ -59,7 +55,7 @@ jobs:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install tools (search engine)
|
||||
run: pip install bandit isort mypy pycodestyle pyflakes pyright
|
||||
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||
|
||||
- name: Gather files (search engine)
|
||||
run: |
|
||||
@@ -69,12 +65,9 @@ jobs:
|
||||
|
||||
- name: Check typings (search engine)
|
||||
run: |
|
||||
curl \
|
||||
-L \
|
||||
-o src/searchengine/nova3/socks.pyi "https://github.com/python/typeshed/raw/refs/heads/main/stubs/PySocks/socks.pyi"
|
||||
MYPYPATH="src/searchengine/nova3" \
|
||||
mypy \
|
||||
--explicit-package-bases \
|
||||
--follow-imports skip \
|
||||
--strict \
|
||||
$PY_FILES
|
||||
pyright \
|
||||
@@ -92,10 +85,6 @@ jobs:
|
||||
--max-line-length=1000 \
|
||||
--statistics \
|
||||
$PY_FILES
|
||||
isort \
|
||||
--check \
|
||||
--diff \
|
||||
$PY_FILES
|
||||
|
||||
- name: Build code (search engine)
|
||||
run: |
|
||||
|
||||
14
.github/workflows/ci_ubuntu.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.6.3"]
|
||||
qt_version: ["6.5.2"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=1G
|
||||
max_size=2G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
@@ -65,9 +65,6 @@ jobs:
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -93,7 +90,7 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
@@ -115,7 +112,7 @@ jobs:
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
@@ -162,7 +159,6 @@ jobs:
|
||||
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
rm -f "${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/sqldrivers/libqsqlmimer.so"
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
rm qbittorrent/apprun-hooks/*
|
||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||
|
||||
11
.github/workflows/ci_webui.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -34,30 +34,21 @@ jobs:
|
||||
run: |
|
||||
npm install
|
||||
npm ls
|
||||
echo "::group::npm ls --all"
|
||||
npm ls --all
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Lint code
|
||||
if: ${{ !cancelled() }}
|
||||
run: npm run lint
|
||||
|
||||
- name: Format code
|
||||
if: ${{ !cancelled() }}
|
||||
run: |
|
||||
npm run format
|
||||
git diff --exit-code
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
config-file: .github/workflows/helper/codeql/js.yaml
|
||||
languages: javascript
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
if: ${{ !cancelled() }}
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
18
.github/workflows/ci_windows.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -67,7 +67,6 @@ jobs:
|
||||
"set(VCPKG_BUILD_TYPE release)")
|
||||
# clear buildtrees after each package installation to reduce disk space requirements
|
||||
$packages = `
|
||||
"boost-build:x64-windows-static-md-release",
|
||||
"openssl:x64-windows-static-md-release",
|
||||
"zlib:x64-windows-static-md-release"
|
||||
${{ env.vcpkg_path }}/vcpkg.exe upgrade `
|
||||
@@ -95,18 +94,11 @@ jobs:
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
|
||||
}
|
||||
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
#.\bootstrap.bat
|
||||
${{ env.vcpkg_path }}/installed/x64-windows-static-md-release/tools/boost-build/b2.exe `
|
||||
stage `
|
||||
toolset=msvc `
|
||||
--stagedir=.\ `
|
||||
--with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.9.1"
|
||||
version: "6.8.0"
|
||||
arch: win64_msvc2022_64
|
||||
archives: qtbase qtsvg qttools
|
||||
cache: true
|
||||
@@ -121,7 +113,7 @@ jobs:
|
||||
${{ env.libtorrent_path }}
|
||||
cd ${{ env.libtorrent_path }}
|
||||
$env:CXXFLAGS+=" /guard:cf"
|
||||
$env:LDFLAGS+=" /GUARD:CF"
|
||||
$env:LDFLAGS+=" /guard:cf"
|
||||
cmake `
|
||||
-B build `
|
||||
-G "Ninja" `
|
||||
@@ -130,7 +122,7 @@ jobs:
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DBUILD_SHARED_LIBS=OFF `
|
||||
-Ddeprecated-functions=OFF `
|
||||
-Dstatic_runtime=OFF `
|
||||
@@ -147,7 +139,7 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/install/lib/cmake/LibtorrentRasterbar" `
|
||||
-DMSVC_RUNTIME_DYNAMIC=ON `
|
||||
-DTESTING=ON `
|
||||
|
||||
13
.github/workflows/coverity-scan.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
matrix:
|
||||
libt_version: ["2.0.11"]
|
||||
qbt_gui: ["GUI=ON"]
|
||||
qt_version: ["6.9.1"]
|
||||
qt_version: ["6.5.2"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "88"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
@@ -52,9 +52,6 @@ jobs:
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -77,7 +74,7 @@ jobs:
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
@@ -101,7 +98,7 @@ jobs:
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
PATH="${{ env.coverity_path }}/bin:$PATH" \
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
# exception statement from your version.
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Optional
|
||||
import argparse
|
||||
import re
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import sys
|
||||
|
||||
|
||||
def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None:
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
# exception statement from your version.
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
|
||||
@@ -19,7 +19,7 @@ repos:
|
||||
- ts
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks.git
|
||||
rev: v6.0.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-json
|
||||
name: Check JSON files
|
||||
@@ -69,11 +69,11 @@ repos:
|
||||
- ts
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell.git
|
||||
rev: v2.4.1
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
name: Check spelling (codespell)
|
||||
args: ["--ignore-words-list", "additionals,categor,curren,fo,indexIn,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
|
||||
args: ["--ignore-words-list", "additionals,categor,curren,fo,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.*\.desktop |
|
||||
@@ -88,7 +88,7 @@ repos:
|
||||
- ts
|
||||
|
||||
- repo: https://github.com/crate-ci/typos.git
|
||||
rev: v1.35.3
|
||||
rev: v1.29.4
|
||||
hooks:
|
||||
- id: typos
|
||||
name: Check spelling (typos)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v51x]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
@@ -9,7 +9,7 @@ type = QT
|
||||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v51x]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
|
||||
@@ -8,7 +8,7 @@ project(qBittorrent
|
||||
|
||||
# version requirements - older versions may work, but you are on your own
|
||||
set(minBoostVersion 1.76)
|
||||
set(minQt6Version 6.6.0)
|
||||
set(minQt6Version 6.5.0)
|
||||
set(minOpenSSLVersion 3.0.2)
|
||||
set(minLibtorrent1Version 1.2.19)
|
||||
set(minLibtorrentVersion 2.0.10)
|
||||
|
||||
139
Changelog
@@ -1,4 +1,141 @@
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
|
||||
Wed Nov 19th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.4
|
||||
- WEBUI: Fixes a regression in v5.1.3 (Chocobo1)
|
||||
|
||||
Tue Nov 11th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.3
|
||||
- BUGFIX: Don't leave an empty folder when deleting or moving torrents (Ryu481)
|
||||
- BUGFIX: Fix invalid Transifex links (rekayno)
|
||||
- BUGFIX: Don't fail because of existing files when exporting torrent files (glassez)
|
||||
- BUGFIX: Allow equals character in the command line value (Mark Yu)
|
||||
- BUGFIX: Fix "Save as .torrent file" button being visible before metadata retrieved (glassez)
|
||||
- BUGFIX: Fix crash related with the processing order of libtorrent alerts (glassez)
|
||||
- BUGFIX: Fix screen reader accessibility in torrent list (Andrew Johnson)
|
||||
- BUGFIX: Improve tab key focus in hidable tab bar widget (Andrew Johnson)
|
||||
- WEBUI: Fix http header affecting reverse proxy (Chocobo1)
|
||||
- WEBAPI: Use native separators for path autofill suggestions (glassez)
|
||||
- SEARCH: Plugin updater should work again now (Chocobo1)
|
||||
- WINDOWS: Program update checker should work again now (Cloudflare related) (Chocobo1)
|
||||
- WINDOWS: NSIS: Add Catalan and Kurdish translations (Ramon López i Cros, Halbast)
|
||||
- LINUX: Fix crashes related to getrandom() on specific setups (Chocobo1)
|
||||
- MACOS: Fix system language autodetection on MacOS (Ryu481)
|
||||
|
||||
Wed Jul 02nd 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.2
|
||||
- BUGFIX: Don't expose palette colors in UI theme editor since they are not customizable (glassez)
|
||||
- BUGFIX: Add fallback to update mechanism (sledgehammer999)
|
||||
- WEBUI: Fix incorrectly backported changes (glassez)
|
||||
- WEBAPI: Trim leading whitespaces on Run External Program fields (Chocobo1)
|
||||
- RSS/SEARCH: Prevent opening local files if web page is expected (glassez)
|
||||
- MACOS: Make qBittorrent quit on MacOS with main window closed (Ryu481)
|
||||
|
||||
Mon Jun 23rd 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.1
|
||||
- BUGFIX: Don't interpret wildcard pattern as filepath globbing (glassez)
|
||||
- BUGFIX: Fix appearance of search history length spinbox (glassez)
|
||||
- BUGFIX: Remove dubious seeding time max value (glassez)
|
||||
- BUGFIX: Fix ratio handling (glassez)
|
||||
- BUGFIX: Fix compilation with Qt 6.6.0 (glassez)
|
||||
- WEBUI: Make General tab text selectable by default (dezza)
|
||||
- WEBUI: Add versioning to local preferences (Chocobo1)
|
||||
- WEBUI: Make multi-rename search & replace fields use a monospace font (Atk)
|
||||
- WEBUI: Fix wrong replacement sequence in IPv6 string (Chocobo1)
|
||||
- WEBUI: Fix memory leak (bolshoytoster)
|
||||
- WEBUI: Fix path autofill in set location and new category (tehcneko)
|
||||
- RSS: Mark matched article as "read" if it refers to a duplicate torrent (glassez)
|
||||
- WINDOWS: Update command line help message (KanishkaHalder1771)
|
||||
- WINDOWS: NSIS: Don't require agreement on the license page (Chocobo1)
|
||||
- LINUX: Fix preview not opening on Wayland (Isak05)
|
||||
- LINUX: Add fallback for random number generator (Chocobo1)
|
||||
|
||||
Sun Apr 27th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
|
||||
- FEATURE: Enable customizing the save statistics time interval (Burnerelu)
|
||||
- FEATURE: Add drag support to torrent content widget (Chocobo1)
|
||||
- FEATURE: Display External IP Address in status bar (Thomas Piccirello)
|
||||
- FEATURE: Use modern functions to get random numbers under Linux/Windows (security related) (Chocobo1)
|
||||
- FEATURE: Add eXact Length parameter when creating magnet URI (antanilol)
|
||||
- FEATURE: Support fetching tracker list from URL (Thomas Piccirello)
|
||||
- FEATURE: Add `announce_port` support (Maxime Thiebaut)
|
||||
- BUGFIX: Enable adaptive step size for upload and download limits (Harald Nordgren)
|
||||
- BUGFIX: Add URL link for reverse proxy setup examples (Chocobo1)
|
||||
- BUGFIX: Allow drop action only on transfer list (Chocobo1)
|
||||
- BUGFIX: Fix the tab order in dialogs (thalieht)
|
||||
- BUGFIX: Fix filesize sorting in preview dialog (DoubleSpicy)
|
||||
- BUGFIX: Improve the speed icons in the status bar (Mahdi Hosseinzadeh)
|
||||
- BUGFIX: Update link to news (tinyboxvk)
|
||||
- BUGFIX: Fix tab stop order in various dialogs and UI elements (Chocobo1)
|
||||
- BUGFIX: Make links accessible by keyboard (Chocobo1)
|
||||
- BUGFIX: Make tab key switch focus (Chocobo1)
|
||||
- BUGFIX: Revise DHT bootstrap node list (stalkerok, Chocobo1)
|
||||
- BUGFIX: Return first tracker as fallback for "current tracker" (glassez)
|
||||
- BUGFIX: Prevent crash when exiting app with `Add torrent` dialogs opened (glassez)
|
||||
- BUGFIX: Fix torrent relocating files when switching to "manual" mode (glassez)
|
||||
- BUGFIX: Prevent crash due to corrupted resume data (glassez)
|
||||
- WEBUI: Improvements that should help with assistive technologies (Chocobo1)
|
||||
- WEBUI: Internal refactoring to migrate away from MooTools and towards native browser APIs (Chocobo1, skomerko)
|
||||
- WEBUI: Implement path autocompletion (Paweł Kotiuk)
|
||||
- WEBUI: Implement double-click behavior controls (Hanabishi)
|
||||
- WEBUI: Add ability to toggle alternating row colors in tables (skomerko)
|
||||
- WEBUI: Improve visibility of unread RSS articles (skomerko)
|
||||
- WEBUI: Remove deleted torrents even if they are currently filtered out (Carmelo Scandaliato)
|
||||
- WEBUI: Highlight torrent category in context menu (skomerko)
|
||||
- WEBUI: Implement 'Auto hide zero status filters' (skomerko)
|
||||
- WEBUI: Allow to filter torrent list by save path (skomerko)
|
||||
- WEBUI: Handle regex syntax error for torrent filtering (HamletDuFromage)
|
||||
- WEBUI: Add missing icons (skomerko)
|
||||
- WEBUI: Add link to 'List of alternative WebUI' wiki page in Options (Chocobo1)
|
||||
- WEBUI: Improve properties panel, torrent deletion dialog, filter list, subcategories, torrent deletion, statistics window (skomerko)
|
||||
- WEBUI: Allow to display only hostname in the Tracker column (skomerko)
|
||||
- WEBUI: Show country/region name next to its flag (skomerko)
|
||||
- WEBUI: Improve hash copy actions in context menu (skomerko)
|
||||
- WEBUI: Support removing tracker from all torrents in WebUI/WebAPI (Thomas Piccirello)
|
||||
- WEBUI: Display DHT information in the Status bar only when DHT is enabled (skomerko)
|
||||
- WEBUI: Add 'Confirm torrent recheck' option (skomerko)
|
||||
- WEBUI: Support managing web seeds (Thomas Piccirello)
|
||||
- WEBUI: Add colors to log table rows (skomerko)
|
||||
- WEBUI: Prevent text selection within tabs, menu items (skomerko)
|
||||
- WEBUI: Use correct text and background colors in RSS details view (skomerko)
|
||||
- WEBUI: Reduce padding in torrents table (skomerko)
|
||||
- WEBUI: Add WebAPI/WebUI for managing cookies (Thomas Piccirello)
|
||||
- WEBUI: Support updating RSS feed URL (Thomas Piccirello)
|
||||
- WEBUI: Add 'Engine' column to Search table (skomerko)
|
||||
- WEBUI: Add confirm dialog for Auto TMM (skomerko)
|
||||
- WEBUI: Add context menu to search tabs (skomerko)
|
||||
- WEBUI: Show file filter when Content tab selected on load (Thomas Piccirello)
|
||||
- WEBUI: DHT, PeX and LSD rows are now always on top in Trackers table (skomerko)
|
||||
- WEBUI: Clear properties panel when torrent no longer selected (skomerko)
|
||||
- WEBUI: Support auto resizing table columns (Thomas Piccirello)
|
||||
- WEBUI: Fix displaying RSS panel on load (Thomas Piccirello)
|
||||
- WEBUI: Add tooltip to regex filter button (Patrik Elfström)
|
||||
- WEBUI: Hide context menu when clicking on a table row (Patrik Elfström)
|
||||
- WEBUI: Display torrent progress percentage in General tab (skomerko)
|
||||
- WEBUI: Use thin scrollbars (skomerko)
|
||||
- WEBUI: Show 'Rename...' context menu item only when one torrent is selected (skomerko)
|
||||
- WEBUI: Display error when download fails (Thomas Piccirello)
|
||||
- WEBUI: Add colors to 'Status' column in Trackers table (skomerko)
|
||||
- WEBUI: Add missing icon to 'Queue' context menu item (skomerko)
|
||||
- WEBUI: Change filter inputs to type search (Patrik Elfström)
|
||||
- WEBUI: Allow to move state icon to name column in torrents table (skomerko)
|
||||
- WEBUI: Fix bug where the 'Tracker editing' dialog displays incorrect data (skomerko)
|
||||
- WEBUI: Maintain row highlight after rearranging table columns (skomerko)
|
||||
- WEBUI: Fix preferences not applied in magnet handler (Chocobo1)
|
||||
- WEBUI: Update sort icon after changing column order (skomerko)
|
||||
- WEBUI: Show 'Edit tracker URL...' only when one tracker is selected (skomerko)
|
||||
- WEBUI: Set status filter to 'All' if selected filter is no longer visible (skomerko)
|
||||
- WEBAPI: Don't reannounce when removing tracker via WebAPI (Thomas Piccirello)
|
||||
- WEBAPI: Add WebAPI for managing torrent webseeds (Thomas Piccirello)
|
||||
- WEBAPI: Add `forced` parameter to `torrents/add` (Chris B)
|
||||
- WEBAPI: Optionally include trackers list in torrent info response (ze0s)
|
||||
- WEBAPI: Add new method `setTags` to upsert tags on torrents (ze0s)
|
||||
- RSS: Resolve relative URLs within RSS article description (Zentino)
|
||||
- SEARCH: Provide SSL context field (Chocobo1)
|
||||
- SEARCH: Allow to refresh existing search (glassez)
|
||||
- SEARCH: Allow multiple simultaneous searches (glassez)
|
||||
- SEARCH: Store opened search tabs (glassez)
|
||||
- SEARCH: Store search history (glassez)
|
||||
- SEARCH: Migrate socks.py from SocksiPy to PySocks 1.7.1 (FredBill1)
|
||||
- SEARCH: Bump Python version minimum requirement (Chocobo1)
|
||||
- WINDOWS: Opt into Windows SegmentHeap (Andarwinux)
|
||||
- WINDOWS: Allow to choose color scheme on Windows (glassez)
|
||||
- WINDOWS: Verify hash of Python installer (Chocobo1)
|
||||
- LINUX: Add support for Thunar file manager (algebnaly)
|
||||
- MACOS: Fix shift-click selection on macOS (Luke Memet)
|
||||
|
||||
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
|
||||
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
|
||||
|
||||
2
INSTALL
@@ -11,7 +11,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
||||
|
||||
- OpenSSL >= 3.0.2
|
||||
|
||||
- Qt 6.6.0 - 6.x
|
||||
- Qt 6.5.0 - 6.x
|
||||
|
||||
- zlib >= 1.2.11
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# WebAPI Changelog
|
||||
|
||||
## 2.14.0
|
||||
* [#23202](https://github.com/qbittorrent/qBittorrent/pull/23202)
|
||||
* WebAPI responds with the error message "Endpoint does not exist" when the endpoint does not exist, to better differentiate from unrelated Not Found (i.e. 404) responses
|
||||
* `auth/login` endpoint responds to invalid credentials with a 401
|
||||
* `torrents/add` endpoint responds with `success_count`, `pending_count`, `failure_count`, and `added_torrent_ids`
|
||||
* When `pending_count` is non-zero, response code 202 is used
|
||||
* When all torrents fail to be added, response code 409 is used
|
||||
|
||||
## 2.13.1
|
||||
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
|
||||
* `torrents/add` endpoint now supports downloading from a search plugin via the `downloader` parameter
|
||||
* `torrents/fetchMetadata` endpoint now supports fetching from a search plugin via the `downloader` parameter
|
||||
* [#23088](https://github.com/qbittorrent/qBittorrent/pull/23088)
|
||||
* Add `clientdata/load` and `clientdata/store` endpoints for managing WebUI-specific client settings and other shared data
|
||||
|
||||
## 2.13.0
|
||||
* [#23045](https://github.com/qbittorrent/qBittorrent/pull/23045)
|
||||
* `torrents/trackers` returns three new fields: `next_announce`, `min_announce` and `endpoints`
|
||||
* `endpoints` is an array of tracker endpoints, each with `name`, `updating`, `status`, `msg`, `bt_version`, `num_peers`, `num_peers`, `num_leeches`, `num_downloaded`, `next_announce` and `min_announce` fields
|
||||
* `torrents/trackers` now returns `5` and `6` in `status` field as possible values
|
||||
* `5` for `Tracker error` and `6` for `Unreachable`
|
||||
* [#22963](https://github.com/qbittorrent/qBittorrent/pull/22963)
|
||||
* `torrents/editTracker` endpoint now supports setting a tracker's tier via `tier` parameter
|
||||
* `torrents/editTracker` endpoint always responds with a 204 when successful
|
||||
* `torrents/editTracker` endpoint `origUrl` parameter renamed to `url`
|
||||
* [#23061](https://github.com/qbittorrent/qBittorrent/pull/23061)
|
||||
* `sync/torrentPeers` returns one new field: `i2p_dest`, only when the peer is from I2P
|
||||
* In this case, the fields `ip` and `port` are not returned
|
||||
* [#23085](https://github.com/qbittorrent/qBittorrent/pull/23085)
|
||||
* `torrents/parseMetadata` now responds with an array of metadata in the same order as the files in the request. It previously responded with an object keyed off of the submitted file name.
|
||||
|
||||
## 2.12.1
|
||||
* [#23031](https://github.com/qbittorrent/qBittorrent/pull/23031)
|
||||
* Add `torrents/setComment` endpoint with parameters `hashes` and `comment` for setting a new torrent comment
|
||||
|
||||
## 2.12.0
|
||||
|
||||
* [#22989](https://github.com/qbittorrent/qBittorrent/pull/22989)
|
||||
* `sync/maindata` returns one new field: `share_limit_action`
|
||||
* `torrents/setShareLimits` now requires a new `shareLimitAction` param that sets a torrent's shareLimitAction property
|
||||
* possible values `Default`, `Stop`, `Remove`, `RemoveWithContent` and `EnableSuperSeeding`
|
||||
|
||||
## 2.11.10
|
||||
|
||||
* [#22958](https://github.com/qbittorrent/qBittorrent/pull/22958)
|
||||
* `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined`
|
||||
* [#22954](https://github.com/qbittorrent/qBittorrent/pull/22954)
|
||||
* `torrents/reannounce` supports specifying individual trackers via `trackers` field
|
||||
|
||||
## 2.11.9
|
||||
|
||||
* [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015)
|
||||
* Add `torrents/fetchMetadata` endpoint for retrieving torrent metadata associated with a URL
|
||||
* Add `torrents/parseMetadata` endpoint for retrieving torrent metadata associated with a .torrent file
|
||||
* Add `torrents/saveMetadata` endpoint for saving retrieved torrent metadata to a .torrent file
|
||||
* `torrents/add` allows adding a torrent with metadata previously retrieved via `torrents/fetchMetadata` or `torrents/parseMetadata`
|
||||
* `torrents/add` allows specifying a torrent's file priorities
|
||||
* [#22698](https://github.com/qbittorrent/qBittorrent/pull/22698)
|
||||
* `torrents/addTrackers` and `torrents/removeTrackers` now accept `hash=all` and adds/removes the tracker to/from *all* torrents
|
||||
* For compatibility, `torrents/removeTrackers` still accepts `hash=*` internally we transform it into `all`
|
||||
* Allow passing a pipe (`|`) separated list of hashes in `hash` for `torrents/addTrackers` and `torrents/removeTrackers`
|
||||
|
||||
## 2.11.8
|
||||
|
||||
* [#21349](https://github.com/qbittorrent/qBittorrent/pull/21349)
|
||||
* Handle sending `204 No Content` status code when response contains no data
|
||||
* Some endpoints still return `200 OK` to ensure smooth transition
|
||||
* [#22750](https://github.com/qbittorrent/qBittorrent/pull/22750)
|
||||
* `torrents/info` allows an optional parameter `includeFiles` that defaults to `false`
|
||||
* Each torrent will contain a new key `files` which will list all files similar to the `torrents/files` endpoint
|
||||
* [#22813](https://github.com/qbittorrent/qBittorrent/pull/22813)
|
||||
* `app/getDirectoryContent` allows an optional parameter `withMetadata` to send file metadata
|
||||
* Fields are `name`, `type`, `size`, `creation_date`, `last_access_date`, `last_modification_date`
|
||||
* See PR for TypeScript types
|
||||
|
||||
## 2.11.7
|
||||
|
||||
* [#22166](https://github.com/qbittorrent/qBittorrent/pull/22166)
|
||||
* `sync/maindata` returns 3 new torrent fields: `has_tracker_warning`, `has_tracker_error`, `has_other_announce_error`
|
||||
|
||||
## 2.11.6
|
||||
|
||||
* [#22460](https://github.com/qbittorrent/qBittorrent/pull/22460)
|
||||
* `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present
|
||||
* `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present
|
||||
* `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present
|
||||
@@ -20,11 +20,10 @@ target_compile_features(qbt_common_cfg INTERFACE
|
||||
)
|
||||
|
||||
target_compile_definitions(qbt_common_cfg INTERFACE
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060600
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060500
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
QT_NO_CAST_TO_ASCII
|
||||
QT_NO_CAST_FROM_BYTEARRAY
|
||||
QT_NO_CONTEXTLESS_CONNECT
|
||||
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||
QT_USE_QSTRINGBUILDER
|
||||
QT_STRICT_ITERATORS
|
||||
@@ -90,7 +89,7 @@ if (MSVC)
|
||||
/Zc:__cplusplus
|
||||
)
|
||||
target_link_options(qbt_common_cfg INTERFACE
|
||||
/GUARD:CF
|
||||
/guard:cf
|
||||
$<$<NOT:$<CONFIG:Debug>>:/OPT:REF /OPT:ICF>
|
||||
# suppress linking warning due to /INCREMENTAL and /OPT:ICF being both ON
|
||||
$<$<CONFIG:RelWithDebInfo>:/INCREMENTAL:NO>
|
||||
|
||||
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.2.0</string>
|
||||
<string>5.1.4</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
BIN
dist/mac/qBitTorrentDocument.icns
vendored
BIN
dist/mac/qbittorrent_mac.icns
vendored
9
dist/unix/CMakeLists.txt
vendored
@@ -34,12 +34,12 @@ endforeach()
|
||||
|
||||
if (GUI)
|
||||
install(FILES org.qbittorrent.qBittorrent.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
|
||||
COMPONENT data
|
||||
)
|
||||
|
||||
install(FILES org.qbittorrent.qBittorrent.metainfo.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/
|
||||
COMPONENT data
|
||||
)
|
||||
|
||||
@@ -55,9 +55,4 @@ if (GUI)
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status
|
||||
COMPONENT data
|
||||
)
|
||||
else()
|
||||
install(FILES org.qbittorrent.qBittorrent-nox.metainfo.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo
|
||||
COMPONENT data
|
||||
)
|
||||
endif()
|
||||
|
||||
BIN
dist/unix/menuicons/16x16/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 750 B |
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 750 B |
BIN
dist/unix/menuicons/192x192/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
BIN
dist/unix/menuicons/24x24/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
dist/unix/menuicons/32x32/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
dist/unix/menuicons/36x36/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
dist/unix/menuicons/64x64/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
dist/unix/menuicons/72x72/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
dist/unix/menuicons/96x96/apps/qbittorrent.png
vendored
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
|
||||
<component type="console-application">
|
||||
<id>org.qbittorrent.qBittorrent-nox</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-or-later and OpenSSL</project_license>
|
||||
<name>qBittorrent-nox</name>
|
||||
<summary>An open-source Bittorrent client (nox version)</summary>
|
||||
<description>
|
||||
<p>
|
||||
The qBittorrent project aims to provide an open-source software alternative to µTorrent.
|
||||
Additionally, qBittorrent runs and provides the same features on all major platforms (FreeBSD, Linux, macOS, OS/2, Windows).
|
||||
qBittorrent is based on the Qt toolkit and libtorrent-rasterbar library.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Polished µTorrent-like User Interface</li>
|
||||
<li>Well-integrated and extensible Search Engine</li>
|
||||
<li>RSS feed support with advanced download filters (incl. regex)</li>
|
||||
<li>Many Bittorrent extensions supported</li>
|
||||
<li>Remote control through Web user interface, written with AJAX</li>
|
||||
<li>Sequential downloading (Download in order)</li>
|
||||
<li>Advanced control over torrents, trackers and peers</li>
|
||||
<li>Bandwidth scheduler</li>
|
||||
<li>Torrent creation tool</li>
|
||||
<li>IP Filtering (eMule & PeerGuardian format compatible)</li>
|
||||
<li>IPv6 compliant</li>
|
||||
<li>UPnP / NAT-PMP port forwarding support</li>
|
||||
<li>Available on all platforms: Windows, Linux, macOS, FreeBSD, OS/2</li>
|
||||
<li>Available in ~70 languages</li>
|
||||
</ul>
|
||||
</description>
|
||||
<provides>
|
||||
<binary>qbittorrent-nox</binary>
|
||||
</provides>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Running headless (nox) version</caption>
|
||||
<image>https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/43fcf4550f567c38fb879b984922b659e90982cc/src/img/screenshots/linux/5.webp</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
|
||||
<developer id="org.qbittorrent">
|
||||
<name>The qBittorrent Project</name>
|
||||
</developer>
|
||||
<url type="homepage">https://www.qbittorrent.org/</url>
|
||||
<url type="bugtracker">https://bugs.qbittorrent.org/</url>
|
||||
<url type="faq">https://wiki.qbittorrent.org/Frequently-Asked-Questions</url>
|
||||
<url type="help">https://forum.qbittorrent.org/</url>
|
||||
<url type="donation">https://www.qbittorrent.org/donate</url>
|
||||
<url type="translate">https://wiki.qbittorrent.org/How-to-translate-qBittorrent</url>
|
||||
<url type="vcs-browser">https://github.com/qbittorrent/qBittorrent</url>
|
||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="5.2.0~alpha1" date="2025-02-11"/>
|
||||
</releases>
|
||||
</component>
|
||||
@@ -161,8 +161,8 @@ Name[ta]=qBittorrent
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
Name[te]=qBittorrent
|
||||
GenericName[th]=ไคลเอนต์บิททอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่านบิตทอร์เรนต์
|
||||
GenericName[th]=ไคลเอนต์บิตทอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแบ่งปันไฟล์ผ่านไฟล์บิตทอร์เรนต์
|
||||
Name[th]=qBittorrent
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
|
||||
@@ -62,6 +62,6 @@
|
||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="5.2.0~alpha1" date="2025-02-11"/>
|
||||
<release version="5.1.4" date="2025-11-19"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
2
dist/windows/config.nsh
vendored
@@ -14,7 +14,7 @@
|
||||
; 4.5.1.3 -> good
|
||||
; 4.5.1.3.2 -> bad
|
||||
; 4.5.0beta -> bad
|
||||
!define /ifndef QBT_VERSION "5.2.0"
|
||||
!define /ifndef QBT_VERSION "5.1.4"
|
||||
|
||||
; Option that controls the installer's window name
|
||||
; If set, its value will be used like this:
|
||||
|
||||
54
dist/windows/installer-translations/catalan.nsh
vendored
@@ -1,60 +1,60 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_CATALAN} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_CATALAN} "qBittorrent (requerit)"
|
||||
;LangString inst_desktop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_CATALAN} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_CATALAN} "Crea una drecera a l'escriptori"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_CATALAN} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_CATALAN} "Crea una drecera al menú d'inici"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_CATALAN} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_CATALAN} "Inicia qBittorrent en iniciar Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_CATALAN} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_CATALAN} "Obre els fitxers .torrent amb qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_CATALAN} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_CATALAN} "Obre els enllaços magnètics amb qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_CATALAN} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_CATALAN} "Afegir la regla del tallafoc del Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_CATALAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_CATALAN} "Deshabilita el límit de longitud del path de Windows (260 caràcters de limitació MAX_PATH, requereix Windows 10 1607 o posterior)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_CATALAN} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_CATALAN} "S'està afegint la regla del tallafoc del Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_CATALAN} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_CATALAN} "qBittorrent s'està executant. Tanqueu l'aplicació abans d'instal·lar."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_CATALAN} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_CATALAN} "La versió actual es desinstal·larà. La configuració de l'usuari i els torrents es mantindran intactes."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_CATALAN} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_CATALAN} "Desinstal·lant la versió anterior."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_CATALAN} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_CATALAN} "Executa qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_CATALAN} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_CATALAN} "Aquest instal·lador només funciona en versions Windows de 64 bits."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_CATALAN} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_CATALAN} "Aquest instal·lador requereix almenys Windows 10 (1809) / Windows Server 2019."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_CATALAN} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_CATALAN} "Desinstal·lar qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_CATALAN} "Remove files"
|
||||
LangString remove_files ${LANG_CATALAN} "Elimina els fitxers"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_CATALAN} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_CATALAN} "Elimina les draceres"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_CATALAN} "Remove file associations"
|
||||
LangString remove_associations ${LANG_CATALAN} "Elimina les associacions de fitxers"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_CATALAN} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_CATALAN} "Elimina les claus del registre"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_CATALAN} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_CATALAN} "Elimina els fitxers de configuració"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_CATALAN} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_CATALAN} "Elimina la regla del tallafoc del Windows"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_CATALAN} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_CATALAN} "Eliminant la regla del tallafoc del Windows"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_CATALAN} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_CATALAN} "Elimina els torrents i les dades de la memòria cau"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_CATALAN} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_CATALAN} "qBittorrent s'està executant. Tanca l'aplicació abans de desinstal·lar-la."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_CATALAN} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_CATALAN} "No s'està eliminant l'associació .torrent. Està associat a:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_CATALAN} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_CATALAN} "No s'està eliminant l'associació magnètica. Està associat a:"
|
||||
|
||||
54
dist/windows/installer-translations/kurdish.nsh
vendored
@@ -1,60 +1,60 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_KURDISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_KURDISH} "کیووبیتتۆرێنت (پێویستە)"
|
||||
;LangString inst_desktop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_KURDISH} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_KURDISH} "قەدبڕێک دروست بکە لەسەر دێسکتۆپ"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_KURDISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_KURDISH} "قەدبڕێک دروست بکە لەسەر پێڕستی دەتپێک"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_KURDISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_KURDISH} "لەگەڵ هەڵبوونی کۆمپیوتەرەکە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_KURDISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_KURDISH} "پەڕگەکانی .torrent بە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_KURDISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_KURDISH} "بەسەرە موگناتیسییەکان بە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_KURDISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز زیاد بکە"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_KURDISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_KURDISH} "سنووری درێژیی ڕێڕەوی ویندۆز ناچالاک بکە (سنووری ٢٦٠ پیتی، پێویستی بە ویندۆزی ١٠ و دواتر هەیە)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_KURDISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز زیاد دەبێت"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_KURDISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_KURDISH} "کیووبیتتۆرێنت کارایە. تکایە بەرنامەکە دابخەرەوە پێش دامەزراندن."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_KURDISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_KURDISH} "ئەم وەشانەی ئێستا دەسڕدرێتەوە. ڕێکخستنەکانی بەکارهێنەر و تۆرێنتەکان وەک خۆیان دەمێننەوە."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_KURDISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_KURDISH} "وەشانی پێشوو دەسڕدرێتەوە."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_KURDISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_KURDISH} "کیووبیتتۆرێنت کارا بکە."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_KURDISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_KURDISH} "ئەم دامەزرێنەرە تەنیا لەسەر وەشانی ٦٤ بیتی ویندۆز کار دەکات."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_KURDISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_KURDISH} "ئەم دامەزرێنەرە لانیکەم پێویستی بە ویندۆزی ١٠ یان ویندۆز سێرڤەری ٢٠١٩ هەیە."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_KURDISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_KURDISH} "کیووبیتتۆرێنت بسڕەوە"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_KURDISH} "Remove files"
|
||||
LangString remove_files ${LANG_KURDISH} "پەڕگەکان بسڕەوە"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_KURDISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_KURDISH} "قەدبڕەکان بسڕەوە"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_KURDISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_KURDISH} "پەیوەندیی پەڕگەکان بسڕەوە"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_KURDISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_KURDISH} "کلیلەکانی تۆمار بسڕەوە"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_KURDISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_KURDISH} "پەڕگەکانی شێوەپێدان بسڕەوە"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_KURDISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز بسڕەوە"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_KURDISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز دەسڕێتەوە"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_KURDISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_KURDISH} "تۆرێنت و داتای کاشکراو بسڕەوە"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_KURDISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_KURDISH} "کیووبیتتۆرێنت کارایە. تکایە بەرنامەکە دابخە پێش سڕینەوەی."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_KURDISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_KURDISH} "پەیوەندیی .torrent ناسڕێتەوە. پەیوەندیی هەیە بە:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_KURDISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_KURDISH} "پەیوەندیی موگناتیسی ناسڕێتەوە. پەیوەیندیی هەیە بە:"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.\" Automatically generated by Pandoc 3.7.0.2
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT\-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt"
|
||||
.SH NAME
|
||||
@@ -26,7 +26,7 @@ compatible).
|
||||
qBittorrent\-nox is meant to be controlled via its feature\-rich Web UI
|
||||
which is accessible as a default on http://localhost:8080.
|
||||
The Web UI access is secured and the default account user name is
|
||||
\(lqadmin\(rq with \(lqadminadmin\(rq as a password.
|
||||
\[lq]admin\[rq] with \[lq]adminadmin\[rq] as a password.
|
||||
.SH OPTIONS
|
||||
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
|
||||
.PP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.\" Automatically generated by Pandoc 3.7.0.2
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt"
|
||||
.SH NAME
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.\" Automatically generated by Pandoc 3.7.0.2
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT\-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки"
|
||||
.SH НАЗВАНИЕ
|
||||
qBittorrent\-nox \(em клиент сети БитТоррент для командной строки.
|
||||
qBittorrent\-nox \[em] клиент сети БитТоррент для командной строки.
|
||||
.SH АВТОРЫ
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
.\" Automatically generated by Pandoc 3.7.0.2
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент"
|
||||
.SH НАЗВАНИЕ
|
||||
qBittorrent \(em клиент сети БитТоррент.
|
||||
qBittorrent \[em] клиент сети БитТоррент.
|
||||
.SH АВТОРЫ
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
|
||||
@@ -410,7 +410,7 @@ void Application::setMemoryWorkingSetLimit(const int size)
|
||||
return;
|
||||
|
||||
m_storeMemoryWorkingSetLimit = size;
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
}
|
||||
@@ -575,9 +575,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
case u'L':
|
||||
str.replace(i, 2, torrent->category());
|
||||
break;
|
||||
case u'M':
|
||||
str.replace(i, 2, torrent->comment());
|
||||
break;
|
||||
case u'N':
|
||||
str.replace(i, 2, torrent->name());
|
||||
break;
|
||||
@@ -662,13 +659,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
{
|
||||
// strip redundant quotes
|
||||
if (arg.startsWith(u'"') && arg.endsWith(u'"'))
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
arg.slice(1, (arg.size() - 2));
|
||||
#else
|
||||
arg.removeLast().removeFirst();
|
||||
#endif
|
||||
}
|
||||
arg = arg.mid(1, (arg.size() - 2));
|
||||
|
||||
arg = replaceVariables(arg);
|
||||
}
|
||||
@@ -677,7 +668,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
||||
QProcess proc;
|
||||
proc.setProgram(command);
|
||||
proc.setArguments(args);
|
||||
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
|
||||
if (proc.startDetached())
|
||||
{
|
||||
@@ -847,7 +837,7 @@ int Application::exec()
|
||||
printf("%s\n", qUtf8Printable(loadingStr));
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
|
||||
@@ -1205,7 +1195,7 @@ void Application::shutdownCleanup([[maybe_unused]] QSessionManager &manager)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
void Application::applyMemoryWorkingSetLimit() const
|
||||
{
|
||||
const size_t MiB = 1024 * 1024;
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace
|
||||
};
|
||||
|
||||
// Option with string value. May not have a shortcut
|
||||
class StringOption : protected Option
|
||||
struct StringOption : protected Option
|
||||
{
|
||||
public:
|
||||
explicit constexpr StringOption(const QStringView name)
|
||||
@@ -467,13 +467,13 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
return result;
|
||||
}
|
||||
|
||||
QString wrapText(const QString &text, const int initialIndentation = USAGE_TEXT_COLUMN, const int wrapAtColumn = WRAP_AT_COLUMN)
|
||||
QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN, int wrapAtColumn = WRAP_AT_COLUMN)
|
||||
{
|
||||
const QStringList words = text.split(u' ');
|
||||
QStringList words = text.split(u' ');
|
||||
QStringList lines = {words.first()};
|
||||
int currentLineMaxLength = wrapAtColumn - initialIndentation;
|
||||
|
||||
for (const QString &word : asConst(words.sliced(1)))
|
||||
for (const QString &word : asConst(words.mid(1)))
|
||||
{
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
|
||||
{
|
||||
|
||||
@@ -175,15 +175,12 @@ void FileLogger::flushLog()
|
||||
|
||||
void FileLogger::openLogFile()
|
||||
{
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)
|
||||
|| !m_logFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner))
|
||||
{
|
||||
LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled. File: \"%1\". Error: \"%2\".")
|
||||
.arg(m_logFile.fileName(), m_logFile.errorString()), Log::CRITICAL);
|
||||
return;
|
||||
m_logFile.close();
|
||||
LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
|
||||
}
|
||||
|
||||
// best effort, don't report error
|
||||
m_logFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
|
||||
}
|
||||
|
||||
void FileLogger::closeLogFile()
|
||||
|
||||
@@ -182,6 +182,9 @@ int main(int argc, char *argv[])
|
||||
adjustFileDescriptorLimit();
|
||||
#endif
|
||||
|
||||
// We must save it here because QApplication constructor may change it
|
||||
const bool isOneArg = (argc == 2);
|
||||
|
||||
// `app` must be declared out of try block to allow display message box in case of exception
|
||||
std::unique_ptr<Application> app;
|
||||
try
|
||||
@@ -201,27 +204,34 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
const QBtCommandLineParameters params = app->commandLineArgs();
|
||||
|
||||
// "show help/version" takes priority over other flags
|
||||
if (params.showHelp)
|
||||
{
|
||||
displayUsage(QString::fromLocal8Bit(argv[0]));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
if (params.showVersion)
|
||||
{
|
||||
displayVersion();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!params.unknownParameter.isEmpty())
|
||||
{
|
||||
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 is an unknown command line parameter.",
|
||||
"--random-parameter is an unknown command line parameter.")
|
||||
.arg(params.unknownParameter));
|
||||
}
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
if (params.showVersion)
|
||||
{
|
||||
if (isOneArg)
|
||||
{
|
||||
displayVersion();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
|
||||
.arg(u"-v (or --version)"_s));
|
||||
}
|
||||
#endif
|
||||
if (params.showHelp)
|
||||
{
|
||||
if (isOneArg)
|
||||
{
|
||||
displayUsage(QString::fromLocal8Bit(argv[0]));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
|
||||
.arg(u"-h (or --help)"_s));
|
||||
}
|
||||
|
||||
// Check if qBittorrent is already running
|
||||
if (app->hasAnotherInstance())
|
||||
|
||||
@@ -55,7 +55,6 @@ add_library(qbt_base STATIC
|
||||
concepts/stringable.h
|
||||
digest32.h
|
||||
exceptions.h
|
||||
freediskspacechecker.h
|
||||
global.h
|
||||
http/connection.h
|
||||
http/httperror.h
|
||||
@@ -161,7 +160,6 @@ add_library(qbt_base STATIC
|
||||
bittorrent/trackerentry.cpp
|
||||
bittorrent/trackerentrystatus.cpp
|
||||
exceptions.cpp
|
||||
freediskspacechecker.cpp
|
||||
http/connection.cpp
|
||||
http/httperror.cpp
|
||||
http/requestparser.cpp
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "addtorrentmanager.h"
|
||||
|
||||
#include "base/bittorrent/addtorrenterror.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
@@ -186,8 +185,8 @@ void AddTorrentManager::handleDuplicateTorrent(const QString &source
|
||||
message = tr("Trackers are merged from new source");
|
||||
}
|
||||
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: \"%2\". Torrent infohash: %3. Result: %4")
|
||||
.arg(source, existingTorrent->name(), existingTorrent->infoHash().toString(), message));
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||
.arg(source, existingTorrent->name(), message));
|
||||
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "base/applicationcomponent.h"
|
||||
#include "base/bittorrent/addtorrenterror.h"
|
||||
#include "base/bittorrent/addtorrentparams.h"
|
||||
#include "base/torrentfileguard.h"
|
||||
|
||||
@@ -44,7 +45,6 @@ namespace BitTorrent
|
||||
class Session;
|
||||
class Torrent;
|
||||
class TorrentDescriptor;
|
||||
struct AddTorrentError;
|
||||
}
|
||||
|
||||
namespace Net
|
||||
|
||||
@@ -189,13 +189,8 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
|
||||
return;
|
||||
}
|
||||
|
||||
QHash<TorrentID, qsizetype> registeredTorrentsIndexes;
|
||||
registeredTorrentsIndexes.reserve(m_registeredTorrents.length());
|
||||
for (qsizetype i = 0; i < m_registeredTorrents.length(); ++i)
|
||||
registeredTorrentsIndexes.insert(m_registeredTorrents.at(i), i);
|
||||
|
||||
const QRegularExpression hashPattern {u"^([A-Fa-f0-9]{40})$"_s};
|
||||
qsizetype queuePos = 0;
|
||||
int start = 0;
|
||||
while (true)
|
||||
{
|
||||
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
|
||||
@@ -206,15 +201,11 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
|
||||
if (rxMatch.hasMatch())
|
||||
{
|
||||
const auto torrentID = BitTorrent::TorrentID::fromString(rxMatch.captured(1));
|
||||
const qsizetype pos = registeredTorrentsIndexes.value(torrentID, -1);
|
||||
const int pos = m_registeredTorrents.indexOf(torrentID, start);
|
||||
if (pos != -1)
|
||||
{
|
||||
if (pos != queuePos)
|
||||
{
|
||||
m_registeredTorrents.swapItemsAt(pos, queuePos);
|
||||
registeredTorrentsIndexes.insert(m_registeredTorrents.at(pos), pos);
|
||||
}
|
||||
++queuePos;
|
||||
std::swap(m_registeredTorrents[start], m_registeredTorrents[pos]);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,7 +227,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||
LoadTorrentParams torrentParams;
|
||||
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
|
||||
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name"));
|
||||
torrentParams.comment = fromLTString(resumeDataRoot.dict_find_string_value("qBt-comment"));
|
||||
torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
|
||||
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
|
||||
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
|
||||
@@ -352,9 +342,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||
return torrentParams;
|
||||
}
|
||||
|
||||
void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, LoadTorrentParams resumeData) const
|
||||
void BitTorrent::BencodeResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData = std::move(resumeData)]
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]()
|
||||
{
|
||||
m_asyncWorker->store(id, resumeData);
|
||||
});
|
||||
@@ -438,7 +428,6 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
|
||||
data["qBt-category"] = resumeData.category.toStdString();
|
||||
data["qBt-tags"] = setToEntryList(resumeData.tags);
|
||||
data["qBt-name"] = resumeData.name.toStdString();
|
||||
data["qBt-comment"] = resumeData.comment.toStdString();
|
||||
data["qBt-seedStatus"] = resumeData.hasFinishedStatus;
|
||||
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
|
||||
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace BitTorrent
|
||||
|
||||
QList<TorrentID> registeredTorrents() const override;
|
||||
LoadResumeDataResult load(const TorrentID &id) const override;
|
||||
void store(const TorrentID &id, LoadTorrentParams resumeData) const override;
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
|
||||
void remove(const TorrentID &id) const override;
|
||||
void storeQueue(const QList<TorrentID> &queue) const override;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObj
|
||||
|
||||
QJsonObject BitTorrent::CategoryOptions::toJSON() const
|
||||
{
|
||||
QJsonValue downloadPathValue = QJsonValue::Null;
|
||||
QJsonValue downloadPathValue = QJsonValue::Undefined;
|
||||
if (downloadPath)
|
||||
{
|
||||
if (downloadPath->enabled)
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace
|
||||
{
|
||||
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
|
||||
|
||||
const int DB_VERSION = 9;
|
||||
const int DB_VERSION = 8;
|
||||
|
||||
const QString DB_TABLE_META = u"meta"_s;
|
||||
const QString DB_TABLE_TORRENTS = u"torrents"_s;
|
||||
@@ -86,7 +86,7 @@ namespace
|
||||
class StoreJob final : public Job
|
||||
{
|
||||
public:
|
||||
StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData);
|
||||
StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
|
||||
void perform(QSqlDatabase db) override;
|
||||
|
||||
private:
|
||||
@@ -131,7 +131,6 @@ namespace
|
||||
const Column DB_COLUMN_NAME = makeColumn(u"name"_s);
|
||||
const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s);
|
||||
const Column DB_COLUMN_TAGS = makeColumn(u"tags"_s);
|
||||
const Column DB_COLUMN_COMMENT = makeColumn(u"comment"_s);
|
||||
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn(u"target_save_path"_s);
|
||||
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn(u"download_path"_s);
|
||||
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn(u"content_layout"_s);
|
||||
@@ -232,7 +231,7 @@ namespace BitTorrent
|
||||
void run() override;
|
||||
void requestInterruption();
|
||||
|
||||
void store(const TorrentID &id, LoadTorrentParams resumeData);
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
|
||||
void remove(const TorrentID &id);
|
||||
void storeQueue(const QList<TorrentID> &queue);
|
||||
|
||||
@@ -328,9 +327,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor
|
||||
return parseQueryResultRow(query);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, LoadTorrentParams resumeData) const
|
||||
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
{
|
||||
m_asyncWorker->store(id, std::move(resumeData));
|
||||
m_asyncWorker->store(id, resumeData);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const
|
||||
@@ -462,7 +461,6 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_TAGS, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_COMMENT, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, u"TEXT NOT NULL"_s),
|
||||
@@ -580,9 +578,6 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
|
||||
if (fromVersion <= 8)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_COMMENT, u"TEXT"_s);
|
||||
|
||||
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||
if (!query.prepare(updateMetaVersionQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
@@ -624,7 +619,6 @@ LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &q
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||
resumeData.comment = query.value(DB_COLUMN_COMMENT.name).toString();
|
||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||
if (!tagsData.isEmpty())
|
||||
{
|
||||
@@ -775,9 +769,9 @@ void DBResumeDataStorage::Worker::requestInterruption()
|
||||
m_waitCondition.wakeAll();
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, LoadTorrentParams resumeData)
|
||||
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData)
|
||||
{
|
||||
addJob(std::make_unique<StoreJob>(id, std::move(resumeData)));
|
||||
addJob(std::make_unique<StoreJob>(id, resumeData));
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
|
||||
@@ -803,9 +797,9 @@ namespace
|
||||
{
|
||||
using namespace BitTorrent;
|
||||
|
||||
StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData)
|
||||
StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
|
||||
: m_torrentID {torrentID}
|
||||
, m_resumeData {std::move(resumeData)}
|
||||
, m_resumeData {resumeData}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -840,7 +834,6 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData)
|
||||
DB_COLUMN_NAME,
|
||||
DB_COLUMN_CATEGORY,
|
||||
DB_COLUMN_TAGS,
|
||||
DB_COLUMN_COMMENT,
|
||||
DB_COLUMN_TARGET_SAVE_PATH,
|
||||
DB_COLUMN_DOWNLOAD_PATH,
|
||||
DB_COLUMN_CONTENT_LAYOUT,
|
||||
@@ -906,7 +899,6 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData)
|
||||
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
|
||||
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
|
||||
? QString() : Utils::String::joinIntoString(m_resumeData.tags, u","_s)));
|
||||
query.bindValue(DB_COLUMN_COMMENT.placeholder, m_resumeData.comment);
|
||||
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout));
|
||||
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
|
||||
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace BitTorrent
|
||||
QList<TorrentID> registeredTorrents() const override;
|
||||
LoadResumeDataResult load(const TorrentID &id) const override;
|
||||
|
||||
void store(const TorrentID &id, LoadTorrentParams resumeData) const override;
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
|
||||
void remove(const TorrentID &id) const override;
|
||||
void storeQueue(const QList<TorrentID> &queue) const override;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas@piccirello.com>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas@piccirello.com>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -27,14 +27,13 @@
|
||||
*/
|
||||
|
||||
#include "filesearcher.h"
|
||||
|
||||
#include <QPromise>
|
||||
|
||||
#include "base/bittorrent/common.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
|
||||
namespace
|
||||
void FileSearcher::search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
|
||||
, const Path &savePath, const Path &downloadPath, const bool forceAppendExt)
|
||||
{
|
||||
bool findInDir(const Path &dirPath, PathList &fileNames, const bool forceAppendExt)
|
||||
const auto findInDir = [](const Path &dirPath, PathList &fileNames, const bool forceAppendExt) -> bool
|
||||
{
|
||||
bool found = false;
|
||||
for (Path &fileName : fileNames)
|
||||
@@ -59,12 +58,8 @@ namespace
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void FileSearcher::search(const PathList &originalFileNames, const Path &savePath
|
||||
, const Path &downloadPath, const bool forceAppendExt, QPromise<FileSearchResult> &promise)
|
||||
{
|
||||
Path usedPath = savePath;
|
||||
PathList adjustedFileNames = originalFileNames;
|
||||
const bool found = findInDir(usedPath, adjustedFileNames, (forceAppendExt && downloadPath.isEmpty()));
|
||||
@@ -74,5 +69,5 @@ void FileSearcher::search(const PathList &originalFileNames, const Path &savePat
|
||||
findInDir(usedPath, adjustedFileNames, forceAppendExt);
|
||||
}
|
||||
|
||||
promise.addResult(FileSearchResult {.savePath = usedPath, .fileNames = adjustedFileNames});
|
||||
emit searchFinished(id, usedPath, adjustedFileNames);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,13 +32,10 @@
|
||||
|
||||
#include "base/path.h"
|
||||
|
||||
template <typename T> class QPromise;
|
||||
|
||||
struct FileSearchResult
|
||||
namespace BitTorrent
|
||||
{
|
||||
Path savePath;
|
||||
PathList fileNames;
|
||||
};
|
||||
class TorrentID;
|
||||
}
|
||||
|
||||
class FileSearcher final : public QObject
|
||||
{
|
||||
@@ -46,8 +43,12 @@ class FileSearcher final : public QObject
|
||||
Q_DISABLE_COPY_MOVE(FileSearcher)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
FileSearcher() = default;
|
||||
|
||||
void search(const PathList &originalFileNames, const Path &savePath
|
||||
, const Path &downloadPath, bool forceAppendExt, QPromise<FileSearchResult> &promise);
|
||||
public slots:
|
||||
void search(const BitTorrent::TorrentID &id, const PathList &originalFileNames
|
||||
, const Path &savePath, const Path &downloadPath, bool forceAppendExt);
|
||||
|
||||
signals:
|
||||
void searchFinished(const BitTorrent::TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
};
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
#include "infohash.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "base/global.h"
|
||||
|
||||
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
|
||||
|
||||
@@ -89,28 +86,6 @@ BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
|
||||
#endif
|
||||
}
|
||||
|
||||
QString BitTorrent::InfoHash::toString() const
|
||||
{
|
||||
// Returns a string that is suitable for logging purpose
|
||||
|
||||
QString ret;
|
||||
ret.reserve(40 + 64 + 2); // v1 hash length + v2 hash length + comma
|
||||
|
||||
const SHA1Hash v1Hash = v1();
|
||||
const bool v1IsValid = v1Hash.isValid();
|
||||
if (v1IsValid)
|
||||
ret += v1Hash.toString();
|
||||
|
||||
if (const SHA256Hash v2Hash = v2(); v2Hash.isValid())
|
||||
{
|
||||
if (v1IsValid)
|
||||
ret += u", ";
|
||||
ret += v2Hash.toString();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BitTorrent::InfoHash::operator WrappedType() const
|
||||
{
|
||||
return m_nativeHash;
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
|
||||
#include "base/digest32.h"
|
||||
|
||||
class QString;
|
||||
|
||||
using SHA1Hash = Digest32<160>;
|
||||
using SHA256Hash = Digest32<256>;
|
||||
|
||||
@@ -81,8 +79,6 @@ namespace BitTorrent
|
||||
SHA256Hash v2() const;
|
||||
TorrentID toTorrentID() const;
|
||||
|
||||
QString toString() const;
|
||||
|
||||
operator WrappedType() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -50,7 +50,6 @@ namespace BitTorrent
|
||||
TagSet tags;
|
||||
Path savePath;
|
||||
Path downloadPath;
|
||||
QString comment;
|
||||
TorrentContentLayout contentLayout = TorrentContentLayout::Original;
|
||||
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
|
||||
bool useAutoTMM = false;
|
||||
|
||||
@@ -39,11 +39,7 @@ PeerAddress PeerAddress::parse(const QStringView address)
|
||||
if (address.startsWith(u'[') && address.contains(u"]:"))
|
||||
{ // IPv6
|
||||
ipPort = address.split(u"]:");
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
ipPort[0].slice(1); // chop '['
|
||||
#else
|
||||
ipPort[0] = ipPort[0].sliced(1); // chop '['
|
||||
#endif
|
||||
ipPort[0] = ipPort[0].mid(1); // chop '['
|
||||
}
|
||||
else if (address.contains(u':'))
|
||||
{ // IPv4
|
||||
|
||||
@@ -71,8 +71,8 @@ QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedRe
|
||||
return loadedResumeData;
|
||||
}
|
||||
|
||||
void BitTorrent::ResumeDataStorage::onResumeDataLoaded(const TorrentID &torrentID, LoadResumeDataResult loadResumeDataResult) const
|
||||
void BitTorrent::ResumeDataStorage::onResumeDataLoaded(const TorrentID &torrentID, const LoadResumeDataResult &loadResumeDataResult) const
|
||||
{
|
||||
const QMutexLocker locker {&m_loadedResumeDataMutex};
|
||||
m_loadedResumeData.append({.torrentID = torrentID, .result = std::move(loadResumeDataResult)});
|
||||
m_loadedResumeData.append({torrentID, loadResumeDataResult});
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace BitTorrent
|
||||
|
||||
virtual QList<TorrentID> registeredTorrents() const = 0;
|
||||
virtual LoadResumeDataResult load(const TorrentID &id) const = 0;
|
||||
virtual void store(const TorrentID &id, LoadTorrentParams resumeData) const = 0;
|
||||
virtual void store(const TorrentID &id, const LoadTorrentParams &resumeData) const = 0;
|
||||
virtual void remove(const TorrentID &id) const = 0;
|
||||
virtual void storeQueue(const QList<TorrentID> &queue) const = 0;
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace BitTorrent
|
||||
void loadFinished();
|
||||
|
||||
protected:
|
||||
void onResumeDataLoaded(const TorrentID &torrentID, LoadResumeDataResult loadResumeDataResult) const;
|
||||
void onResumeDataLoaded(const TorrentID &torrentID, const LoadResumeDataResult &loadResumeDataResult) const;
|
||||
|
||||
private:
|
||||
virtual void doLoadAll() const = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -422,8 +422,6 @@ namespace BitTorrent
|
||||
virtual void setUTPRateLimited(bool limited) = 0;
|
||||
virtual MixedModeAlgorithm utpMixedMode() const = 0;
|
||||
virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0;
|
||||
virtual int hostnameCacheTTL() const = 0;
|
||||
virtual void setHostnameCacheTTL(int value) = 0;
|
||||
virtual bool isIDNSupportEnabled() const = 0;
|
||||
virtual void setIDNSupportEnabled(bool enabled) = 0;
|
||||
virtual bool multiConnectionsPerIpEnabled() const = 0;
|
||||
@@ -482,8 +480,6 @@ namespace BitTorrent
|
||||
virtual QString lastExternalIPv4Address() const = 0;
|
||||
virtual QString lastExternalIPv6Address() const = 0;
|
||||
|
||||
virtual qint64 freeDiskSpace() const = 0;
|
||||
|
||||
signals:
|
||||
void startupProgressUpdated(int progress);
|
||||
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
|
||||
@@ -493,6 +489,7 @@ namespace BitTorrent
|
||||
void categoryOptionsChanged(const QString &categoryName);
|
||||
void fullDiskError(Torrent *torrent, const QString &msg);
|
||||
void IPFilterParsed(bool error, int ruleCount);
|
||||
void loadTorrentFailed(const QString &error);
|
||||
void metadataDownloaded(const TorrentInfo &info);
|
||||
void restored();
|
||||
void paused();
|
||||
@@ -523,6 +520,5 @@ namespace BitTorrent
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers);
|
||||
void freeDiskSpaceChecked(qint64 result);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -30,7 +30,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -62,16 +61,11 @@ class QString;
|
||||
class QTimer;
|
||||
class QUrl;
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
class BandwidthScheduler;
|
||||
class FileSearcher;
|
||||
class FilterParserThread;
|
||||
class FreeDiskSpaceChecker;
|
||||
class NativeSessionExtension;
|
||||
|
||||
struct FileSearchResult;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class MoveStorageMode;
|
||||
@@ -396,8 +390,6 @@ namespace BitTorrent
|
||||
void setUTPRateLimited(bool limited) override;
|
||||
MixedModeAlgorithm utpMixedMode() const override;
|
||||
void setUtpMixedMode(MixedModeAlgorithm mode) override;
|
||||
int hostnameCacheTTL() const override;
|
||||
void setHostnameCacheTTL(int value) override;
|
||||
bool isIDNSupportEnabled() const override;
|
||||
void setIDNSupportEnabled(bool enabled) override;
|
||||
bool multiConnectionsPerIpEnabled() const override;
|
||||
@@ -456,8 +448,6 @@ namespace BitTorrent
|
||||
QString lastExternalIPv4Address() const override;
|
||||
QString lastExternalIPv6Address() const override;
|
||||
|
||||
qint64 freeDiskSpace() const override;
|
||||
|
||||
// Torrent interface
|
||||
void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
|
||||
void handleTorrentShareLimitChanged(TorrentImpl *torrent);
|
||||
@@ -477,15 +467,14 @@ namespace BitTorrent
|
||||
void handleTorrentTrackersChanged(TorrentImpl *torrent);
|
||||
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
|
||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *torrent, LoadTorrentParams data);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
|
||||
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
|
||||
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
|
||||
|
||||
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
|
||||
|
||||
lt::torrent_handle reloadTorrent(const lt::torrent_handle ¤tHandle, lt::add_torrent_params params);
|
||||
|
||||
QFuture<FileSearchResult> findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths = {}) const;
|
||||
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
|
||||
, const Path &downloadPath, const PathList &filePaths = {}) const;
|
||||
|
||||
void enablePortMapping();
|
||||
void disablePortMapping();
|
||||
@@ -520,6 +509,7 @@ namespace BitTorrent
|
||||
void generateResumeData();
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||
|
||||
private:
|
||||
@@ -578,7 +568,8 @@ namespace BitTorrent
|
||||
void updateSeedingLimitTimer();
|
||||
void exportTorrentFile(const Torrent *torrent, const Path &folderPath);
|
||||
|
||||
void handleAlert(lt::alert *alert);
|
||||
void handleAlert(const lt::alert *alert);
|
||||
void dispatchTorrentAlert(const lt::torrent_alert *alert);
|
||||
void handleAddTorrentAlert(const lt::add_torrent_alert *alert);
|
||||
void handleStateUpdateAlert(const lt::state_update_alert *alert);
|
||||
void handleMetadataReceivedAlert(const lt::metadata_received_alert *alert);
|
||||
@@ -605,21 +596,9 @@ namespace BitTorrent
|
||||
void handleTrackerAlert(const lt::tracker_alert *alert);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert);
|
||||
void handleFilePrioAlert(const lt::file_prio_alert *alert);
|
||||
#endif
|
||||
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *alert);
|
||||
void handleFileCompletedAlert(const lt::file_completed_alert *alert);
|
||||
void handleFileRenamedAlert(const lt::file_renamed_alert *alert);
|
||||
void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *alert);
|
||||
void handlePerformanceAlert(const lt::performance_alert *alert) const;
|
||||
void handleSaveResumeDataAlert(lt::save_resume_data_alert *alert);
|
||||
void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *alert);
|
||||
void handleTorrentCheckedAlert(const lt::torrent_checked_alert *alert);
|
||||
void handleTorrentFinishedAlert(const lt::torrent_finished_alert *alert);
|
||||
|
||||
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, LoadTorrentParams params);
|
||||
TorrentImpl *getTorrent(const lt::torrent_handle &nativeHandle) const;
|
||||
QList<TorrentImpl *> getQueuedTorrentsByID(const QList<TorrentID> &torrentIDs) const;
|
||||
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
|
||||
void saveResumeData();
|
||||
void saveTorrentsQueue();
|
||||
@@ -628,6 +607,7 @@ namespace BitTorrent
|
||||
void populateAdditionalTrackersFromURL();
|
||||
|
||||
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
|
||||
void endAlertSequence(int alertType, qsizetype alertCount);
|
||||
|
||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||
@@ -704,7 +684,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<BTProtocol> m_btProtocol;
|
||||
CachedSettingValue<bool> m_isUTPRateLimited;
|
||||
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
|
||||
CachedSettingValue<int> m_hostnameCacheTTL;
|
||||
CachedSettingValue<bool> m_IDNSupportEnabled;
|
||||
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
|
||||
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
|
||||
@@ -826,13 +805,11 @@ namespace BitTorrent
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||
|
||||
using AddTorrentAlertHandler = std::function<void (const lt::add_torrent_alert *alert)>;
|
||||
QList<AddTorrentAlertHandler> m_addTorrentAlertHandlers;
|
||||
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
|
||||
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
||||
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
||||
QHash<TorrentID, TorrentID> m_changedTorrentIDs;
|
||||
QMap<QString, CategoryOptions> m_categories;
|
||||
@@ -874,10 +851,6 @@ namespace BitTorrent
|
||||
|
||||
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||
|
||||
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
|
||||
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
|
||||
qint64 m_freeDiskSpace = -1;
|
||||
|
||||
friend void Session::initInstance();
|
||||
friend void Session::freeInstance();
|
||||
friend Session *Session::instance();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -45,8 +45,6 @@ class QByteArray;
|
||||
class QDateTime;
|
||||
class QUrl;
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class DownloadPriority;
|
||||
@@ -144,7 +142,6 @@ namespace BitTorrent
|
||||
virtual QDateTime creationDate() const = 0;
|
||||
virtual QString creator() const = 0;
|
||||
virtual QString comment() const = 0;
|
||||
virtual void setComment(const QString &comment) = 0;
|
||||
virtual bool isPrivate() const = 0;
|
||||
virtual qlonglong totalSize() const = 0;
|
||||
virtual qlonglong wantedSize() const = 0;
|
||||
@@ -276,7 +273,10 @@ namespace BitTorrent
|
||||
virtual bool isDHTDisabled() const = 0;
|
||||
virtual bool isPEXDisabled() const = 0;
|
||||
virtual bool isLSDDisabled() const = 0;
|
||||
virtual QList<PeerInfo> peers() const = 0;
|
||||
virtual QBitArray pieces() const = 0;
|
||||
virtual QBitArray downloadingPieces() const = 0;
|
||||
virtual QList<int> pieceAvailability() const = 0;
|
||||
virtual qreal distributedCopies() const = 0;
|
||||
virtual qreal maxRatio() const = 0;
|
||||
virtual int maxSeedingTime() const = 0;
|
||||
@@ -323,10 +323,10 @@ namespace BitTorrent
|
||||
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
|
||||
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
|
||||
|
||||
virtual QFuture<QList<PeerInfo>> fetchPeerInfo() const = 0;
|
||||
virtual QFuture<QList<QUrl>> fetchURLSeeds() const = 0;
|
||||
virtual QFuture<QList<int>> fetchPieceAvailability() const = 0;
|
||||
virtual QFuture<QBitArray> fetchDownloadingPieces() const = 0;
|
||||
virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
|
||||
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
|
||||
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
|
||||
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isRunning() const;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -34,8 +34,6 @@
|
||||
#include "abstractfilestorage.h"
|
||||
#include "downloadpriority.h"
|
||||
|
||||
template <typename T> class QFuture;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentHandler : public QObject, public AbstractFileStorage
|
||||
@@ -54,7 +52,8 @@ namespace BitTorrent
|
||||
* This is not the same as torrrent availability, it is just a fraction of pieces
|
||||
* that can be downloaded right now. It varies between 0 to 1.
|
||||
*/
|
||||
virtual QFuture<QList<qreal>> fetchAvailableFileFractions() const = 0;
|
||||
virtual QList<qreal> availableFileFractions() const = 0;
|
||||
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
|
||||
|
||||
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
|
||||
virtual void flushCache() const = 0;
|
||||
|
||||
@@ -124,20 +124,18 @@ void TorrentCreator::run()
|
||||
// need to sort the file names by natural sort order
|
||||
QStringList dirs = {m_params.sourcePath.data()};
|
||||
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
|
||||
#ifdef Q_OS_WIN
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
// Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
|
||||
#endif
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), dirFilters, QDirIterator::Subdirectories};
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
const QFileInfo dirInfo = dirIter.nextFileInfo();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// .lnk to directory
|
||||
// Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
if (dirInfo.isShortcut())
|
||||
continue;
|
||||
#endif
|
||||
|
||||
const QString dirPath = dirInfo.filePath();
|
||||
dirs.append(dirPath);
|
||||
const QString filePath = dirIter.next();
|
||||
dirs.append(filePath);
|
||||
}
|
||||
std::sort(dirs.begin(), dirs.end(), naturalLessThan);
|
||||
|
||||
@@ -148,29 +146,19 @@ void TorrentCreator::run()
|
||||
{
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter {dir, QDir::Files};
|
||||
#ifdef Q_OS_WIN
|
||||
const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters fileFilters {QDir::Files};
|
||||
#endif
|
||||
QDirIterator fileIter {dir, fileFilters};
|
||||
while (fileIter.hasNext())
|
||||
{
|
||||
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
||||
const Path filePath {fileInfo.filePath()};
|
||||
qint64 fileSize = fileInfo.size();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// .lnk to file
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
if (fileInfo.isShortcut())
|
||||
continue;
|
||||
|
||||
// file symbolic link
|
||||
// QFileInfo::size() failed to return the target file size
|
||||
// and we need to redirect it manually
|
||||
if (fileInfo.isSymbolicLink())
|
||||
fileSize = QFileInfo(fileInfo.symLinkTarget()).size();
|
||||
#endif
|
||||
|
||||
const Path relFilePath = parentPath.relativePathOf(filePath);
|
||||
const Path relFilePath = parentPath.relativePathOf(Path(fileInfo.filePath()));
|
||||
tmpNames.append(relFilePath.toString());
|
||||
fileSizeMap[tmpNames.last()] = fileSize;
|
||||
fileSizeMap[tmpNames.last()] = fileInfo.size();
|
||||
}
|
||||
|
||||
std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan);
|
||||
@@ -186,8 +174,8 @@ void TorrentCreator::run()
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
lt::create_torrent newTorrent {fs, m_params.pieceSize, toNativeTorrentFormatFlag(m_params.torrentFormat)};
|
||||
#else
|
||||
lt::create_torrent newTorrent {fs, m_params.pieceSize, m_params.paddedFileSizeLimit
|
||||
, (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})};
|
||||
lt::create_torrent newTorrent(fs, m_params.pieceSize, m_params.paddedFileSizeLimit
|
||||
, (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {}));
|
||||
#endif
|
||||
|
||||
// Add url seeds
|
||||
|
||||
@@ -132,7 +132,7 @@ try
|
||||
const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams);
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
|
||||
if (!result)
|
||||
return nonstd::make_unexpected(result.error());
|
||||
return result.get_unexpected();
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -141,22 +141,6 @@ catch (const lt::system_error &err)
|
||||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
nonstd::expected<QByteArray, QString> BitTorrent::TorrentDescriptor::saveToBuffer() const
|
||||
try
|
||||
{
|
||||
const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams);
|
||||
// usually torrent size should be smaller than 1 MB,
|
||||
// however there are >100 MB v2/hybrid torrent files out in the wild
|
||||
QByteArray buffer;
|
||||
buffer.reserve(1024 * 1024);
|
||||
lt::bencode(std::back_inserter(buffer), torrentEntry);
|
||||
return buffer;
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
{
|
||||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTorrentParams)
|
||||
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
||||
{
|
||||
|
||||
@@ -69,7 +69,6 @@ namespace BitTorrent
|
||||
static nonstd::expected<TorrentDescriptor, QString> loadFromFile(const Path &path) noexcept;
|
||||
static nonstd::expected<TorrentDescriptor, QString> parse(const QString &str) noexcept;
|
||||
nonstd::expected<void, QString> saveToFile(const Path &path) const;
|
||||
nonstd::expected<QByteArray, QString> saveToBuffer() const;
|
||||
|
||||
const lt::add_torrent_params <AddTorrentParams() const;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -37,10 +37,11 @@
|
||||
#endif
|
||||
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/storage_defs.hpp>
|
||||
#include <libtorrent/time.hpp>
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
#include <libtorrent/info_hash.hpp>
|
||||
@@ -50,9 +51,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QCache>
|
||||
#include <QDebug>
|
||||
#include <QFuture>
|
||||
#include <QPointer>
|
||||
#include <QPromise>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
@@ -68,7 +67,6 @@
|
||||
#include "common.h"
|
||||
#include "downloadpriority.h"
|
||||
#include "extensiondata.h"
|
||||
#include "filesearcher.h"
|
||||
#include "loadtorrentparams.h"
|
||||
#include "ltqbitarray.h"
|
||||
#include "lttypecast.h"
|
||||
@@ -151,41 +149,36 @@ namespace
|
||||
|
||||
if (ltAnnounceInfo.updating)
|
||||
{
|
||||
trackerEndpointStatus.isUpdating = true;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Updating;
|
||||
++numUpdating;
|
||||
}
|
||||
else
|
||||
else if (ltAnnounceInfo.fails > 0)
|
||||
{
|
||||
trackerEndpointStatus.isUpdating = false;
|
||||
|
||||
if (ltAnnounceInfo.fails > 0)
|
||||
if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
|
||||
{
|
||||
if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
|
||||
++numTrackerError;
|
||||
}
|
||||
else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
|
||||
++numUnreachable;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
|
||||
++numNotWorking;
|
||||
}
|
||||
trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
|
||||
++numTrackerError;
|
||||
}
|
||||
else if (nativeEntry.verified)
|
||||
else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Working;
|
||||
++numWorking;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
|
||||
++numUnreachable;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
|
||||
++numNotWorking;
|
||||
}
|
||||
}
|
||||
else if (nativeEntry.verified)
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Working;
|
||||
++numWorking;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
|
||||
}
|
||||
|
||||
if (!ltAnnounceInfo.message.empty())
|
||||
{
|
||||
@@ -219,28 +212,23 @@ namespace
|
||||
{
|
||||
if (numUpdating > 0)
|
||||
{
|
||||
trackerEntryStatus.isUpdating = true;
|
||||
trackerEntryStatus.state = TrackerEndpointState::Updating;
|
||||
}
|
||||
else
|
||||
else if (numWorking > 0)
|
||||
{
|
||||
trackerEntryStatus.isUpdating = false;
|
||||
|
||||
if (numWorking > 0)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::Working;
|
||||
}
|
||||
else if (numTrackerError > 0)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::TrackerError;
|
||||
}
|
||||
else if (numUnreachable == numEndpoints)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::Unreachable;
|
||||
}
|
||||
else if ((numUnreachable + numNotWorking) == numEndpoints)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::NotWorking;
|
||||
}
|
||||
trackerEntryStatus.state = TrackerEndpointState::Working;
|
||||
}
|
||||
else if (numTrackerError > 0)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::TrackerError;
|
||||
}
|
||||
else if (numUnreachable == numEndpoints)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::Unreachable;
|
||||
}
|
||||
else if ((numUnreachable + numNotWorking) == numEndpoints)
|
||||
{
|
||||
trackerEntryStatus.state = TrackerEndpointState::NotWorking;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,9 +287,11 @@ namespace
|
||||
|
||||
// TorrentImpl
|
||||
|
||||
TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeHandle, LoadTorrentParams params)
|
||||
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
|
||||
: Torrent(session)
|
||||
, m_session(session)
|
||||
, m_nativeSession(nativeSession)
|
||||
, m_nativeHandle(nativeHandle)
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
, m_infoHash(m_nativeHandle.info_hashes())
|
||||
@@ -324,7 +314,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeH
|
||||
, m_useAutoTMM(params.useAutoTMM)
|
||||
, m_isStopped(params.stopped)
|
||||
, m_sslParams(params.sslParameters)
|
||||
, m_ltAddTorrentParams(std::move(params.ltAddTorrentParams))
|
||||
, m_ltAddTorrentParams(params.ltAddTorrentParams)
|
||||
, m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
|
||||
, m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_limit))
|
||||
{
|
||||
@@ -367,9 +357,6 @@ TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeH
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.comment.isEmpty())
|
||||
m_comment = params.comment;
|
||||
|
||||
setStopCondition(params.stopCondition);
|
||||
|
||||
const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
|
||||
@@ -443,15 +430,6 @@ QString TorrentImpl::comment() const
|
||||
return m_comment;
|
||||
}
|
||||
|
||||
void TorrentImpl::setComment(const QString &comment)
|
||||
{
|
||||
if (m_comment != comment)
|
||||
{
|
||||
m_comment = comment;
|
||||
deferredRequestResumeData();
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentImpl::isPrivate() const
|
||||
{
|
||||
return m_torrentInfo.isPrivate();
|
||||
@@ -1459,7 +1437,7 @@ int TorrentImpl::totalLeechersCount() const
|
||||
|
||||
int TorrentImpl::downloadLimit() const
|
||||
{
|
||||
return m_downloadLimit;
|
||||
return m_downloadLimit;;
|
||||
}
|
||||
|
||||
int TorrentImpl::uploadLimit() const
|
||||
@@ -1487,11 +1465,48 @@ bool TorrentImpl::isLSDDisabled() const
|
||||
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
|
||||
}
|
||||
|
||||
QList<PeerInfo> TorrentImpl::peers() const
|
||||
{
|
||||
std::vector<lt::peer_info> nativePeers;
|
||||
m_nativeHandle.get_peer_info(nativePeers);
|
||||
|
||||
QList<PeerInfo> peers;
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
peers.append(PeerInfo(peer, pieces()));
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
QBitArray TorrentImpl::pieces() const
|
||||
{
|
||||
return m_pieces;
|
||||
}
|
||||
|
||||
QBitArray TorrentImpl::downloadingPieces() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
std::vector<lt::partial_piece_info> queue;
|
||||
m_nativeHandle.get_download_queue(queue);
|
||||
|
||||
QBitArray result {piecesCount()};
|
||||
for (const lt::partial_piece_info &info : queue)
|
||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<int> TorrentImpl::pieceAvailability() const
|
||||
{
|
||||
std::vector<int> avail;
|
||||
m_nativeHandle.piece_availability(avail);
|
||||
|
||||
return {avail.cbegin(), avail.cend()};
|
||||
}
|
||||
|
||||
qreal TorrentImpl::distributedCopies() const
|
||||
{
|
||||
return m_nativeStatus.distributed_copies;
|
||||
@@ -1737,6 +1752,12 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
|
||||
m_nativeHandle.prioritize_pieces(piecePriorities);
|
||||
}
|
||||
|
||||
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
endReceivedMetadataHandling(savePath, fileNames);
|
||||
}
|
||||
|
||||
TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
||||
{
|
||||
const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
|
||||
@@ -1847,7 +1868,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
applyFirstLastPiecePriority(true);
|
||||
|
||||
m_maintenanceJob = MaintenanceJob::None;
|
||||
prepareResumeData(std::move(p));
|
||||
prepareResumeData(p);
|
||||
|
||||
m_session->handleTorrentMetadataReceived(this);
|
||||
}
|
||||
@@ -1856,6 +1877,16 @@ void TorrentImpl::reload()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
const auto queuePos = m_nativeHandle.queue_position();
|
||||
|
||||
m_nativeSession->remove_torrent(m_nativeHandle, lt::session::delete_partfile);
|
||||
|
||||
lt::add_torrent_params p = m_ltAddTorrentParams;
|
||||
p.flags |= lt::torrent_flags::update_subscribe
|
||||
| lt::torrent_flags::override_trackers
|
||||
@@ -1875,21 +1906,19 @@ void TorrentImpl::reload()
|
||||
p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused);
|
||||
}
|
||||
|
||||
const auto queuePos = m_nativeHandle.queue_position();
|
||||
auto *const extensionData = new ExtensionData;
|
||||
p.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
|
||||
m_nativeHandle = m_session->reloadTorrent(m_nativeHandle, std::move(p));
|
||||
m_nativeStatus = static_cast<ExtensionData *>(m_nativeHandle.userdata())->status;
|
||||
m_nativeStatus = extensionData->status;
|
||||
|
||||
if (queuePos >= lt::queue_position_t {})
|
||||
m_nativeHandle.queue_position_set(queuePos);
|
||||
m_nativeStatus.queue_position = queuePos;
|
||||
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
updateState();
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
@@ -2034,7 +2063,7 @@ void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStora
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::handleTorrentChecked()
|
||||
void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
|
||||
{
|
||||
if (!hasMetadata())
|
||||
{
|
||||
@@ -2077,7 +2106,7 @@ void TorrentImpl::handleTorrentChecked()
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::handleTorrentFinished()
|
||||
void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
|
||||
{
|
||||
m_hasMissingFiles = false;
|
||||
if (m_hasFinishedStatus)
|
||||
@@ -2100,29 +2129,37 @@ void TorrentImpl::handleTorrentFinished()
|
||||
m_hasFinishedStatus = true;
|
||||
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
m_moveFinishedTriggers.enqueue([this] { m_session->handleTorrentFinished(this); });
|
||||
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
|
||||
else
|
||||
m_session->handleTorrentFinished(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::handleSaveResumeData(lt::add_torrent_params params)
|
||||
void TorrentImpl::handleTorrentPausedAlert([[maybe_unused]] const lt::torrent_paused_alert *p)
|
||||
{
|
||||
if (m_ltAddTorrentParams.url_seeds != params.url_seeds)
|
||||
}
|
||||
|
||||
void TorrentImpl::handleTorrentResumedAlert([[maybe_unused]] const lt::torrent_resumed_alert *p)
|
||||
{
|
||||
}
|
||||
|
||||
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
{
|
||||
if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
|
||||
{
|
||||
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
|
||||
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
|
||||
// than the list we usually cache, so we have to request it from the appropriate source.
|
||||
fetchURLSeeds().then(this, [this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
}
|
||||
|
||||
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && params.ti)
|
||||
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
|
||||
{
|
||||
Q_ASSERT(m_indexMap.isEmpty());
|
||||
|
||||
const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
|
||||
m_ltAddTorrentParams = std::move(params);
|
||||
m_ltAddTorrentParams = p->params;
|
||||
if (isSeedMode)
|
||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
|
||||
|
||||
@@ -2161,20 +2198,15 @@ void TorrentImpl::handleSaveResumeData(lt::add_torrent_params params)
|
||||
filePaths[i] = Path(it->second);
|
||||
}
|
||||
|
||||
m_session->findIncompleteFiles(savePath(), downloadPath(), filePaths).then(this
|
||||
, [this](const FileSearchResult &result)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
endReceivedMetadataHandling(result.savePath, result.fileNames);
|
||||
});
|
||||
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
|
||||
}
|
||||
else
|
||||
{
|
||||
prepareResumeData(std::move(params));
|
||||
prepareResumeData(p->params);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
|
||||
void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms)
|
||||
{
|
||||
{
|
||||
decltype(params.have_pieces) havePieces;
|
||||
@@ -2198,7 +2230,7 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
|
||||
}
|
||||
|
||||
// Update recent resume data
|
||||
m_ltAddTorrentParams = std::move(params);
|
||||
m_ltAddTorrentParams = params;
|
||||
|
||||
if (needPreserveProgress)
|
||||
{
|
||||
@@ -2214,7 +2246,7 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
|
||||
// We shouldn't save upload_mode flag to allow torrent operate normally on next run
|
||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
|
||||
|
||||
LoadTorrentParams resumeData
|
||||
const LoadTorrentParams resumeData
|
||||
{
|
||||
.ltAddTorrentParams = m_ltAddTorrentParams,
|
||||
.name = m_name,
|
||||
@@ -2222,7 +2254,6 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
|
||||
.tags = m_tags,
|
||||
.savePath = (!m_useAutoTMM ? m_savePath : Path()),
|
||||
.downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()),
|
||||
.comment = m_comment,
|
||||
.contentLayout = m_contentLayout,
|
||||
.operatingMode = m_operatingMode,
|
||||
.useAutoTMM = m_useAutoTMM,
|
||||
@@ -2238,20 +2269,33 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
|
||||
.sslParameters = m_sslParams
|
||||
};
|
||||
|
||||
m_session->handleTorrentResumeDataReady(this, std::move(resumeData));
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFastResumeRejected()
|
||||
void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
|
||||
{
|
||||
if (p->error != lt::errors::resume_data_not_modified)
|
||||
{
|
||||
LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
|
||||
.arg(name(), Utils::String::fromLocal8Bit(p->error.message())), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
|
||||
{
|
||||
// Files were probably moved or storage isn't accessible
|
||||
m_hasMissingFiles = true;
|
||||
LogMsg(tr("Failed to restore torrent. Files were probably moved or storage isn't accessible. Torrent: \"%1\". Reason: \"%2\"")
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::WARNING);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileRenamed(const lt::file_index_t nativeFileIndex, const Path &newActualFilePath, const Path &oldActualFilePath)
|
||||
void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
{
|
||||
const int fileIndex = fileIndexFromNative(nativeFileIndex);
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
const Path newActualFilePath {QString::fromUtf8(p->new_name())};
|
||||
|
||||
const Path oldFilePath = m_filePaths.at(fileIndex);
|
||||
const Path newFilePath = makeUserPath(newActualFilePath);
|
||||
|
||||
@@ -2261,6 +2305,11 @@ void TorrentImpl::handleFileRenamed(const lt::file_index_t nativeFileIndex, cons
|
||||
if (oldFilePath.data() == newFilePath.data())
|
||||
{
|
||||
// Remove empty ".unwanted" folders
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const Path oldActualFilePath {QString::fromUtf8(p->old_name())};
|
||||
#else
|
||||
const Path oldActualFilePath;
|
||||
#endif
|
||||
const Path oldActualParentPath = oldActualFilePath.parentPath();
|
||||
const Path newActualParentPath = newActualFilePath.parentPath();
|
||||
if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
|
||||
@@ -2310,11 +2359,14 @@ void TorrentImpl::handleFileRenamed(const lt::file_index_t nativeFileIndex, cons
|
||||
deferredRequestResumeData();
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileRenameFailed(const lt::file_index_t nativeFileIndex)
|
||||
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
|
||||
{
|
||||
const int fileIndex = fileIndexFromNative(nativeFileIndex);
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
|
||||
.arg(name(), filePath(fileIndex).toString(), Utils::String::fromLocal8Bit(p->error.message())), Log::WARNING);
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
@@ -2322,12 +2374,12 @@ void TorrentImpl::handleFileRenameFailed(const lt::file_index_t nativeFileIndex)
|
||||
deferredRequestResumeData();
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileCompleted(const lt::file_index_t nativeFileIndex)
|
||||
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
return;
|
||||
|
||||
const int fileIndex = fileIndexFromNative(nativeFileIndex);
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
m_completedFiles.setBit(fileIndex);
|
||||
@@ -2355,13 +2407,22 @@ void TorrentImpl::handleFileCompleted(const lt::file_index_t nativeFileIndex)
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileError(FileErrorInfo fileError)
|
||||
void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
|
||||
{
|
||||
m_lastFileError = std::move(fileError);
|
||||
m_lastFileError = {p->error, p->op};
|
||||
}
|
||||
|
||||
void TorrentImpl::handleMetadataReceived()
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
|
||||
{
|
||||
deferredRequestResumeData();
|
||||
}
|
||||
#endif
|
||||
|
||||
void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
|
||||
{
|
||||
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const InfoHash prevInfoHash = infoHash();
|
||||
m_infoHash = InfoHash(m_nativeHandle.info_hashes());
|
||||
@@ -2373,6 +2434,12 @@ void TorrentImpl::handleMetadataReceived()
|
||||
deferredRequestResumeData();
|
||||
}
|
||||
|
||||
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
|
||||
{
|
||||
LogMsg((tr("Performance alert: %1. More info: %2").arg(QString::fromStdString(p->message()), u"https://libtorrent.org/reference-Alerts.html#enum-performance-warning-t"_s))
|
||||
, Log::INFO);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleCategoryOptionsChanged()
|
||||
{
|
||||
if (m_useAutoTMM)
|
||||
@@ -2395,6 +2462,57 @@ void TorrentImpl::handleUnwantedFolderToggled()
|
||||
manageActualFilePaths();
|
||||
}
|
||||
|
||||
void TorrentImpl::handleAlert(const lt::alert *a)
|
||||
{
|
||||
switch (a->type())
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
case lt::file_prio_alert::alert_type:
|
||||
handleFilePrioAlert(static_cast<const lt::file_prio_alert*>(a));
|
||||
break;
|
||||
#endif
|
||||
case lt::file_renamed_alert::alert_type:
|
||||
handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
|
||||
break;
|
||||
case lt::file_rename_failed_alert::alert_type:
|
||||
handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
|
||||
break;
|
||||
case lt::file_completed_alert::alert_type:
|
||||
handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
|
||||
break;
|
||||
case lt::file_error_alert::alert_type:
|
||||
handleFileErrorAlert(static_cast<const lt::file_error_alert*>(a));
|
||||
break;
|
||||
case lt::torrent_finished_alert::alert_type:
|
||||
handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
|
||||
break;
|
||||
case lt::save_resume_data_alert::alert_type:
|
||||
handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
|
||||
break;
|
||||
case lt::save_resume_data_failed_alert::alert_type:
|
||||
handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
|
||||
break;
|
||||
case lt::torrent_paused_alert::alert_type:
|
||||
handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
|
||||
break;
|
||||
case lt::torrent_resumed_alert::alert_type:
|
||||
handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
|
||||
break;
|
||||
case lt::metadata_received_alert::alert_type:
|
||||
handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
|
||||
break;
|
||||
case lt::fastresume_rejected_alert::alert_type:
|
||||
handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
|
||||
break;
|
||||
case lt::torrent_checked_alert::alert_type:
|
||||
handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
|
||||
break;
|
||||
case lt::performance_alert::alert_type:
|
||||
handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::manageActualFilePaths()
|
||||
{
|
||||
const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
|
||||
@@ -2442,11 +2560,6 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
|
||||
return m_nativeHandle;
|
||||
}
|
||||
|
||||
int TorrentImpl::fileIndexFromNative(const lt::file_index_t nativeFileIndex) const
|
||||
{
|
||||
return m_indexMap.value(nativeFileIndex, -1);
|
||||
}
|
||||
|
||||
void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
|
||||
{
|
||||
if (hasMetadata())
|
||||
@@ -2766,9 +2879,18 @@ nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
|
||||
|
||||
try
|
||||
{
|
||||
[[maybe_unused]] const auto infoGuard = qScopeGuard([this] { m_ltAddTorrentParams.ti.reset(); });
|
||||
m_ltAddTorrentParams.ti = info().nativeInfo();
|
||||
return lt::write_torrent_file(m_ltAddTorrentParams);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
|
||||
const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
|
||||
#else
|
||||
const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
|
||||
#endif
|
||||
lt::create_torrent creator {*torrentInfo};
|
||||
|
||||
for (const TrackerEntryStatus &status : asConst(trackers()))
|
||||
creator.add_tracker(status.url.toStdString(), status.tier);
|
||||
|
||||
return creator.generate();
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
{
|
||||
@@ -2780,7 +2902,7 @@ nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
|
||||
{
|
||||
const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
|
||||
if (!preparationResult)
|
||||
return nonstd::make_unexpected(preparationResult.error());
|
||||
return preparationResult.get_unexpected();
|
||||
|
||||
// usually torrent size should be smaller than 1 MB,
|
||||
// however there are >100 MB v2/hybrid torrent files out in the wild
|
||||
@@ -2794,18 +2916,18 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
|
||||
{
|
||||
const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
|
||||
if (!preparationResult)
|
||||
return nonstd::make_unexpected(preparationResult.error());
|
||||
return preparationResult.get_unexpected();
|
||||
|
||||
const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
|
||||
if (!saveResult)
|
||||
return nonstd::make_unexpected(saveResult.error());
|
||||
return saveResult.get_unexpected();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QFuture<QList<PeerInfo>> TorrentImpl::fetchPeerInfo() const
|
||||
void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
|
||||
{
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
|
||||
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2820,12 +2942,13 @@ QFuture<QList<PeerInfo>> TorrentImpl::fetchPeerInfo() const
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
QFuture<QList<QUrl>> TorrentImpl::fetchURLSeeds() const
|
||||
void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
|
||||
{
|
||||
return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2839,12 +2962,13 @@ QFuture<QList<QUrl>> TorrentImpl::fetchURLSeeds() const
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
QFuture<QList<int>> TorrentImpl::fetchPieceAvailability() const
|
||||
void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
|
||||
{
|
||||
return invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2855,12 +2979,13 @@ QFuture<QList<int>> TorrentImpl::fetchPieceAvailability() const
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
QFuture<QBitArray> TorrentImpl::fetchDownloadingPieces() const
|
||||
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
|
||||
{
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2879,12 +3004,13 @@ QFuture<QBitArray> TorrentImpl::fetchDownloadingPieces() const
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
|
||||
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
|
||||
{
|
||||
return invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
|
||||
{
|
||||
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
|
||||
return {};
|
||||
@@ -2918,7 +3044,8 @@ QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
||||
@@ -2929,7 +3056,7 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
||||
Q_ASSERT(priorities.size() == filesCount());
|
||||
|
||||
// Reset 'm_hasSeedStatus' if needed in order to react again to
|
||||
// "torrent finished" event and e.g. show tray notifications
|
||||
// 'torrent_finished_alert' and eg show tray notifications
|
||||
const QList<DownloadPriority> oldPriorities = filePriorities();
|
||||
for (int i = 0; i < oldPriorities.size(); ++i)
|
||||
{
|
||||
@@ -2958,17 +3085,47 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
||||
manageActualFilePaths();
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
QFuture<std::invoke_result_t<Func>> TorrentImpl::invokeAsync(Func &&func) const
|
||||
QList<qreal> TorrentImpl::availableFileFractions() const
|
||||
{
|
||||
QPromise<std::invoke_result_t<Func>> promise;
|
||||
const auto future = promise.future();
|
||||
promise.start();
|
||||
m_session->invokeAsync([func = std::forward<Func>(func), promise = std::move(promise)]() mutable
|
||||
{
|
||||
promise.addResult(func());
|
||||
promise.finish();
|
||||
});
|
||||
Q_ASSERT(hasMetadata());
|
||||
|
||||
return future;
|
||||
const int filesCount = this->filesCount();
|
||||
if (filesCount <= 0) return {};
|
||||
|
||||
const QList<int> piecesAvailability = pieceAvailability();
|
||||
// libtorrent returns empty array for seeding only torrents
|
||||
if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
|
||||
|
||||
QList<qreal> res;
|
||||
res.reserve(filesCount);
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
const TorrentInfo::PieceRange filePieces = m_torrentInfo.filePieces(i);
|
||||
|
||||
int availablePieces = 0;
|
||||
for (const int piece : filePieces)
|
||||
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
|
||||
|
||||
const qreal availability = filePieces.isEmpty()
|
||||
? 1 // the file has no pieces, so it is available by default
|
||||
: static_cast<qreal>(availablePieces) / filePieces.size();
|
||||
res.push_back(availability);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Func, typename Callback>
|
||||
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
|
||||
{
|
||||
m_session->invokeAsync([session = m_session
|
||||
, func = std::move(func)
|
||||
, resultHandler = std::move(resultHandler)
|
||||
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
|
||||
{
|
||||
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
|
||||
{
|
||||
if (thisTorrent)
|
||||
resultHandler(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,7 +94,8 @@ namespace BitTorrent
|
||||
Q_DISABLE_COPY_MOVE(TorrentImpl)
|
||||
|
||||
public:
|
||||
TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeHandle, LoadTorrentParams params);
|
||||
TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
~TorrentImpl() override;
|
||||
|
||||
bool isValid() const;
|
||||
@@ -106,7 +107,6 @@ namespace BitTorrent
|
||||
QDateTime creationDate() const override;
|
||||
QString creator() const override;
|
||||
QString comment() const override;
|
||||
void setComment(const QString &comment) override;
|
||||
bool isPrivate() const override;
|
||||
qlonglong totalSize() const override;
|
||||
qlonglong wantedSize() const override;
|
||||
@@ -203,7 +203,10 @@ namespace BitTorrent
|
||||
bool isDHTDisabled() const override;
|
||||
bool isPEXDisabled() const override;
|
||||
bool isLSDDisabled() const override;
|
||||
QList<PeerInfo> peers() const override;
|
||||
QBitArray pieces() const override;
|
||||
QBitArray downloadingPieces() const override;
|
||||
QList<int> pieceAvailability() const override;
|
||||
qreal distributedCopies() const override;
|
||||
qreal maxRatio() const override;
|
||||
int maxSeedingTime() const override;
|
||||
@@ -217,6 +220,7 @@ namespace BitTorrent
|
||||
int connectionsCount() const override;
|
||||
int connectionsLimit() const override;
|
||||
qlonglong nextAnnounce() const override;
|
||||
QList<qreal> availableFileFractions() const override;
|
||||
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
@@ -254,29 +258,19 @@ namespace BitTorrent
|
||||
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
|
||||
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
|
||||
|
||||
QFuture<QList<PeerInfo>> fetchPeerInfo() const override;
|
||||
QFuture<QList<QUrl>> fetchURLSeeds() const override;
|
||||
QFuture<QList<int>> fetchPieceAvailability() const override;
|
||||
QFuture<QBitArray> fetchDownloadingPieces() const override;
|
||||
QFuture<QList<qreal>> fetchAvailableFileFractions() const override;
|
||||
void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override;
|
||||
void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override;
|
||||
void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override;
|
||||
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
|
||||
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override;
|
||||
|
||||
bool needSaveResumeData() const;
|
||||
|
||||
// Session interface
|
||||
lt::torrent_handle nativeHandle() const;
|
||||
|
||||
int fileIndexFromNative(lt::file_index_t nativeFileIndex) const;
|
||||
|
||||
void handleAlert(const lt::alert *a);
|
||||
void handleStateUpdate(const lt::torrent_status &nativeStatus);
|
||||
void handleFastResumeRejected();
|
||||
void handleFileCompleted(lt::file_index_t nativeFileIndex);
|
||||
void handleFileError(FileErrorInfo fileError);
|
||||
void handleFileRenamed(lt::file_index_t nativeFileIndex, const Path &newActualFilePath, const Path &oldActualFilePath);
|
||||
void handleFileRenameFailed(lt::file_index_t nativeFileIndex);
|
||||
void handleMetadataReceived();
|
||||
void handleSaveResumeData(lt::add_torrent_params params);
|
||||
void handleTorrentChecked();
|
||||
void handleTorrentFinished();
|
||||
void handleQueueingModeChanged();
|
||||
void handleCategoryOptionsChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
@@ -284,6 +278,7 @@ namespace BitTorrent
|
||||
void requestResumeData(lt::resume_data_flags_t flags = {});
|
||||
void deferredRequestResumeData();
|
||||
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
|
||||
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
|
||||
TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
|
||||
void resetTrackerEntryStatuses();
|
||||
|
||||
@@ -296,6 +291,23 @@ namespace BitTorrent
|
||||
void updateProgress();
|
||||
void updateState();
|
||||
|
||||
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
|
||||
void handleFileCompletedAlert(const lt::file_completed_alert *p);
|
||||
void handleFileErrorAlert(const lt::file_error_alert *p);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void handleFilePrioAlert(const lt::file_prio_alert *p);
|
||||
#endif
|
||||
void handleFileRenamedAlert(const lt::file_renamed_alert *p);
|
||||
void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p);
|
||||
void handleMetadataReceivedAlert(const lt::metadata_received_alert *p);
|
||||
void handlePerformanceAlert(const lt::performance_alert *p) const;
|
||||
void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p);
|
||||
void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p);
|
||||
void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p);
|
||||
void handleTorrentFinishedAlert(const lt::torrent_finished_alert *p);
|
||||
void handleTorrentPausedAlert(const lt::torrent_paused_alert *p);
|
||||
void handleTorrentResumedAlert(const lt::torrent_resumed_alert *p);
|
||||
|
||||
bool isMoveInProgress() const;
|
||||
|
||||
void setAutoManaged(bool enable);
|
||||
@@ -308,16 +320,17 @@ namespace BitTorrent
|
||||
void manageActualFilePaths();
|
||||
void applyFirstLastPiecePriority(bool enabled);
|
||||
|
||||
void prepareResumeData(lt::add_torrent_params resumeData);
|
||||
void prepareResumeData(const lt::add_torrent_params ¶ms);
|
||||
void endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames);
|
||||
void reload();
|
||||
|
||||
nonstd::expected<lt::entry, QString> exportTorrent() const;
|
||||
|
||||
template <typename Func>
|
||||
QFuture<std::invoke_result_t<Func>> invokeAsync(Func &&func) const;
|
||||
template <typename Func, typename Callback>
|
||||
void invokeAsync(Func func, Callback resultHandler) const;
|
||||
|
||||
SessionImpl *const m_session = nullptr;
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
lt::torrent_handle m_nativeHandle;
|
||||
mutable lt::torrent_status m_nativeStatus;
|
||||
TorrentState m_state = TorrentState::Unknown;
|
||||
@@ -374,7 +387,7 @@ namespace BitTorrent
|
||||
|
||||
bool m_unchecked = false;
|
||||
|
||||
mutable lt::add_torrent_params m_ltAddTorrentParams;
|
||||
lt::add_torrent_params m_ltAddTorrentParams;
|
||||
|
||||
int m_downloadLimit = 0;
|
||||
int m_uploadLimit = 0;
|
||||
|
||||
@@ -380,7 +380,7 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
// Reached max size, remove a random torrent
|
||||
if (m_torrents.size() >= MAX_TORRENTS)
|
||||
m_torrents.erase(m_torrents.cbegin());
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
}
|
||||
|
||||
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-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
|
||||
@@ -41,6 +41,7 @@ namespace BitTorrent
|
||||
{
|
||||
NotContacted = 1,
|
||||
Working = 2,
|
||||
Updating = 3,
|
||||
NotWorking = 4,
|
||||
TrackerError = 5,
|
||||
Unreachable = 6
|
||||
@@ -51,7 +52,6 @@ namespace BitTorrent
|
||||
QString name {};
|
||||
int btVersion = 1;
|
||||
|
||||
bool isUpdating = false;
|
||||
TrackerEndpointState state = TrackerEndpointState::NotContacted;
|
||||
QString message {};
|
||||
|
||||
@@ -69,7 +69,6 @@ namespace BitTorrent
|
||||
QString url {};
|
||||
int tier = 0;
|
||||
|
||||
bool isUpdating = false;
|
||||
TrackerEndpointState state = TrackerEndpointState::NotContacted;
|
||||
QString message {};
|
||||
|
||||
|
||||
@@ -155,11 +155,7 @@ void Connection::read()
|
||||
sendResponse(resp);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
m_receivedData.slice(result.frameSize);
|
||||
#else
|
||||
m_receivedData.remove(0, result.frameSize);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -31,13 +31,12 @@
|
||||
#include "requestparser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <QByteArrayList>
|
||||
#include <QByteArrayView>
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
@@ -60,19 +59,21 @@ namespace
|
||||
return in;
|
||||
}
|
||||
|
||||
std::optional<QStringPair> parseHeaderLine(const QByteArrayView line)
|
||||
bool parseHeaderLine(const QStringView line, HeaderMap &out)
|
||||
{
|
||||
// [rfc7230] 3.2. Header Fields
|
||||
const int i = line.indexOf(u':');
|
||||
if (i <= 0)
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return std::nullopt;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString name = QString::fromLatin1(line.first(i).trimmed()).toLower();
|
||||
const QString value = QString::fromLatin1(line.sliced(i + 1).trimmed());
|
||||
return {{name, value}};
|
||||
const QString name = line.left(i).trimmed().toString().toLower();
|
||||
const QString value = line.mid(i + 1).trimmed().toString();
|
||||
out[name] = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArrayView data)
|
||||
return {ParseStatus::Incomplete, Request(), 0};
|
||||
}
|
||||
|
||||
const QByteArrayView httpHeaders = data.first(headerEnd);
|
||||
const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
|
||||
if (!parseStartLines(httpHeaders))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||
@@ -151,40 +152,36 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArrayView data)
|
||||
return {ParseStatus::BadMethod, m_request, 0};
|
||||
}
|
||||
|
||||
bool RequestParser::parseStartLines(const QByteArrayView data)
|
||||
bool RequestParser::parseStartLines(const QStringView data)
|
||||
{
|
||||
// we don't handle malformed request which uses `LF` for newline
|
||||
const QList<QByteArrayView> lines = splitToViews(data, CRLF, Qt::SkipEmptyParts);
|
||||
const QList<QStringView> lines = data.split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
|
||||
|
||||
// [rfc7230] 3.2.2. Field Order
|
||||
QByteArrayList requestLines;
|
||||
QStringList requestLines;
|
||||
for (const auto &line : lines)
|
||||
{
|
||||
if (QChar::fromLatin1(line.at(0)).isSpace() && !requestLines.isEmpty())
|
||||
if (line.at(0).isSpace() && !requestLines.isEmpty())
|
||||
{
|
||||
// continuation of previous line
|
||||
requestLines.last() += line;
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLines += line.toByteArray();
|
||||
requestLines += line.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (requestLines.isEmpty())
|
||||
return false;
|
||||
|
||||
if (!parseRequestLine(QString::fromLatin1(requestLines[0])))
|
||||
if (!parseRequestLine(requestLines[0]))
|
||||
return false;
|
||||
|
||||
for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i)
|
||||
{
|
||||
const std::optional<QStringPair> header = parseHeaderLine(*i);
|
||||
if (!header.has_value())
|
||||
if (!parseHeaderLine(*i, m_request.headers))
|
||||
return false;
|
||||
|
||||
const auto [name, value] = header.value();
|
||||
m_request.headers[name] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -207,15 +204,15 @@ bool RequestParser::parseRequestLine(const QString &line)
|
||||
m_request.method = match.captured(1);
|
||||
|
||||
// Request Target
|
||||
const QByteArray url {match.capturedView(2).toLatin1()};
|
||||
const QByteArray url {match.captured(2).toLatin1()};
|
||||
const int sepPos = url.indexOf('?');
|
||||
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).first(sepPos));
|
||||
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).mid(0, sepPos));
|
||||
|
||||
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(asQByteArray(pathComponent)));
|
||||
|
||||
if (sepPos >= 0)
|
||||
{
|
||||
const QByteArrayView query = QByteArrayView(url).sliced(sepPos + 1);
|
||||
const QByteArrayView query = QByteArrayView(url).mid(sepPos + 1);
|
||||
|
||||
// [rfc3986] 2.4 When to Encode or Decode
|
||||
// URL components should be separated before percent-decoding
|
||||
@@ -224,8 +221,8 @@ bool RequestParser::parseRequestLine(const QString &line)
|
||||
const int eqCharPos = param.indexOf('=');
|
||||
if (eqCharPos <= 0) continue; // ignores params without name
|
||||
|
||||
const QByteArrayView nameComponent = param.first(eqCharPos);
|
||||
const QByteArrayView valueComponent = param.sliced(eqCharPos + 1);
|
||||
const QByteArrayView nameComponent = param.mid(0, eqCharPos);
|
||||
const QByteArrayView valueComponent = param.mid(eqCharPos + 1);
|
||||
const QString paramName = QString::fromUtf8(
|
||||
QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' '));
|
||||
const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
|
||||
@@ -273,7 +270,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).sliced(idx + boundaryFieldName.size())).toLatin1();
|
||||
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).mid(idx + boundaryFieldName.size())).toLatin1();
|
||||
if (delimiter.isEmpty())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
|
||||
@@ -282,7 +279,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
|
||||
|
||||
// split data by "dash-boundary"
|
||||
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
|
||||
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter);
|
||||
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
|
||||
if (multipart.isEmpty())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "multipart empty";
|
||||
@@ -313,23 +310,17 @@ bool RequestParser::parseFormData(const QByteArrayView data)
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArrayView headers = data.first(eohPos);
|
||||
const QByteArrayView payload = viewWithoutEndingWith(data.sliced((eohPos + EOH.size())), CRLF);
|
||||
const QString headers = QString::fromLatin1(data.mid(0, eohPos));
|
||||
const QByteArrayView payload = viewWithoutEndingWith(data.mid((eohPos + EOH.size()), data.size()), CRLF);
|
||||
|
||||
HeaderMap headersMap;
|
||||
const QList<QByteArrayView> headerLines = splitToViews(headers, CRLF, Qt::SkipEmptyParts);
|
||||
const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
|
||||
for (const auto &line : headerLines)
|
||||
{
|
||||
const std::optional<QStringPair> header = parseHeaderLine(line);
|
||||
if (!header.has_value())
|
||||
return false;
|
||||
|
||||
const auto [name, value] = header.value();
|
||||
|
||||
if (name == HEADER_CONTENT_DISPOSITION)
|
||||
if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive))
|
||||
{
|
||||
// extract out filename & name
|
||||
const QList<QByteArrayView> directives = splitToViews(line, ";", Qt::SkipEmptyParts);
|
||||
const QList<QStringView> directives = line.split(u';', Qt::SkipEmptyParts);
|
||||
|
||||
for (const auto &directive : directives)
|
||||
{
|
||||
@@ -337,14 +328,15 @@ bool RequestParser::parseFormData(const QByteArrayView data)
|
||||
if (idx < 0)
|
||||
continue;
|
||||
|
||||
const QString name = QString::fromLatin1(directive.first(idx).trimmed()).toLower();
|
||||
const QString value = QString::fromLatin1(unquote(directive.sliced(idx + 1).trimmed()));
|
||||
const QString name = directive.left(idx).trimmed().toString().toLower();
|
||||
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
|
||||
headersMap[name] = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headersMap[name] = value;
|
||||
if (!parseHeaderLine(line, headersMap))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Http
|
||||
RequestParser() = default;
|
||||
|
||||
ParseResult doParse(QByteArrayView data);
|
||||
bool parseStartLines(QByteArrayView data);
|
||||
bool parseStartLines(QStringView data);
|
||||
bool parseRequestLine(const QString &line);
|
||||
|
||||
bool parsePostMessage(QByteArrayView data);
|
||||
|
||||
@@ -96,9 +96,9 @@ void DNSUpdater::ipRequestFinished(const DownloadResult &result)
|
||||
const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data));
|
||||
if (ipRegexMatch.hasMatch())
|
||||
{
|
||||
const QString ipStr = ipRegexMatch.captured(1);
|
||||
QString ipStr = ipRegexMatch.captured(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
|
||||
const QHostAddress newIp {ipStr};
|
||||
QHostAddress newIp(ipStr);
|
||||
if (!newIp.isNull())
|
||||
{
|
||||
if (m_lastIP != newIp)
|
||||
|
||||
@@ -279,13 +279,13 @@ QString Net::DownloadHandlerImpl::errorCodeToString(const QNetworkReply::Network
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return tr("The access to the remote content was denied (403)");
|
||||
return tr("The access to the remote content was denied (401)");
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return tr("The operation requested on the remote content is not permitted");
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return tr("The remote content was not found at the server (404)");
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted (401)");
|
||||
return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
return tr("The Network Access API cannot honor the request because the protocol is not known");
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
|
||||
@@ -265,37 +265,38 @@ void Net::DownloadManager::applyProxySettings()
|
||||
const auto *proxyManager = ProxyConfigurationManager::instance();
|
||||
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
|
||||
|
||||
switch (proxyConfig.type)
|
||||
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
if ((proxyConfig.type == Net::ProxyType::None) || (proxyConfig.type == ProxyType::SOCKS4))
|
||||
return;
|
||||
|
||||
// Proxy enabled
|
||||
if (proxyConfig.type == ProxyType::SOCKS5)
|
||||
{
|
||||
case Net::ProxyType::None:
|
||||
case Net::ProxyType::SOCKS4:
|
||||
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
|
||||
break;
|
||||
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
|
||||
m_proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
|
||||
m_proxy.setType(QNetworkProxy::HttpProxy);
|
||||
}
|
||||
|
||||
case Net::ProxyType::HTTP:
|
||||
m_proxy = QNetworkProxy(
|
||||
QNetworkProxy::HttpProxy
|
||||
, proxyConfig.ip
|
||||
, proxyConfig.port
|
||||
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
|
||||
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
|
||||
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
|
||||
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
|
||||
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
|
||||
break;
|
||||
m_proxy.setHostName(proxyConfig.ip);
|
||||
m_proxy.setPort(proxyConfig.port);
|
||||
|
||||
case Net::ProxyType::SOCKS5:
|
||||
m_proxy = QNetworkProxy(
|
||||
QNetworkProxy::Socks5Proxy
|
||||
, proxyConfig.ip
|
||||
, proxyConfig.port
|
||||
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
|
||||
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
|
||||
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
|
||||
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
|
||||
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
|
||||
break;
|
||||
};
|
||||
// Authentication?
|
||||
if (proxyConfig.authEnabled)
|
||||
{
|
||||
qDebug("Proxy requires authentication, authenticating...");
|
||||
m_proxy.setUser(proxyConfig.username);
|
||||
m_proxy.setPassword(proxyConfig.password);
|
||||
}
|
||||
|
||||
if (proxyConfig.hostnameLookupEnabled)
|
||||
m_proxy.setCapabilities(m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability);
|
||||
else
|
||||
m_proxy.setCapabilities(m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability);
|
||||
}
|
||||
|
||||
void Net::DownloadManager::processWaitingJobs(const ServiceID &serviceID)
|
||||
|
||||
@@ -148,7 +148,8 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
||||
// Encode the body in base64
|
||||
QString crlfBody = body;
|
||||
const QByteArray b = crlfBody.replace(u"\n"_s, u"\r\n"_s).toUtf8().toBase64();
|
||||
for (int i = 0, end = b.length(); i < end; i += 78)
|
||||
const int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
m_message += b.mid(i, 78);
|
||||
m_from = from;
|
||||
m_rcpt = to;
|
||||
@@ -189,12 +190,8 @@ void Smtp::readyRead()
|
||||
{
|
||||
const int pos = m_buffer.indexOf("\r\n");
|
||||
if (pos < 0) return; // Loop exit condition
|
||||
const QByteArray line = m_buffer.first(pos);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
m_buffer.slice(pos + 2);
|
||||
#else
|
||||
const QByteArray line = m_buffer.left(pos);
|
||||
m_buffer.remove(0, (pos + 2));
|
||||
#endif
|
||||
qDebug() << "Response line:" << line;
|
||||
// Extract response code
|
||||
const QByteArray code = line.left(3);
|
||||
|
||||
@@ -94,13 +94,7 @@ bool Path::isValid() const
|
||||
#if defined(Q_OS_WIN)
|
||||
QStringView view = m_pathStr;
|
||||
if (hasDriveLetter(view))
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
view.slice(3);
|
||||
#else
|
||||
view = view.sliced(3);
|
||||
#endif
|
||||
}
|
||||
view = view.mid(3);
|
||||
|
||||
// \\37 is using base-8 number system
|
||||
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
|
||||
@@ -153,9 +147,9 @@ Path Path::rootItem() const
|
||||
#ifdef Q_OS_WIN
|
||||
// should be `c:/` instead of `c:`
|
||||
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
|
||||
return createUnchecked(m_pathStr.first(slashIndex + 1));
|
||||
return createUnchecked(m_pathStr.left(slashIndex + 1));
|
||||
#endif
|
||||
return createUnchecked(m_pathStr.first(slashIndex));
|
||||
return createUnchecked(m_pathStr.left(slashIndex));
|
||||
}
|
||||
|
||||
Path Path::parentPath() const
|
||||
@@ -173,9 +167,9 @@ Path Path::parentPath() const
|
||||
// should be `c:/` instead of `c:`
|
||||
// Windows "drive letter" is limited to one alphabet
|
||||
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
|
||||
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.first(slashIndex + 1));
|
||||
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.left(slashIndex + 1));
|
||||
#endif
|
||||
return createUnchecked(m_pathStr.first(slashIndex));
|
||||
return createUnchecked(m_pathStr.left(slashIndex));
|
||||
}
|
||||
|
||||
QString Path::filename() const
|
||||
@@ -184,7 +178,7 @@ QString Path::filename() const
|
||||
if (slashIndex == -1)
|
||||
return m_pathStr;
|
||||
|
||||
return m_pathStr.sliced(slashIndex + 1);
|
||||
return m_pathStr.mid(slashIndex + 1);
|
||||
}
|
||||
|
||||
QString Path::extension() const
|
||||
@@ -194,9 +188,9 @@ QString Path::extension() const
|
||||
return (u"." + suffix);
|
||||
|
||||
const int slashIndex = m_pathStr.lastIndexOf(u'/');
|
||||
const auto filename = QStringView(m_pathStr).sliced(slashIndex + 1);
|
||||
const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
|
||||
const int dotIndex = filename.lastIndexOf(u'.', -2);
|
||||
return ((dotIndex == -1) ? QString() : filename.sliced(dotIndex).toString());
|
||||
return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
|
||||
}
|
||||
|
||||
bool Path::hasExtension(const QStringView ext) const
|
||||
@@ -299,7 +293,7 @@ Path Path::commonPath(const Path &left, const Path &right)
|
||||
if (commonItemsCount > 0)
|
||||
commonPathSize += (commonItemsCount - 1); // size of intermediate separators
|
||||
|
||||
return Path::createUnchecked(left.m_pathStr.first(commonPathSize));
|
||||
return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
|
||||
}
|
||||
|
||||
Path Path::findRootFolder(const PathList &filePaths)
|
||||
@@ -328,13 +322,7 @@ void Path::stripRootFolder(PathList &filePaths)
|
||||
return;
|
||||
|
||||
for (Path &filePath : filePaths)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
filePath.m_pathStr.slice(commonRootFolder.m_pathStr.size() + 1);
|
||||
#else
|
||||
filePath.m_pathStr.remove(0, (commonRootFolder.m_pathStr.size() + 1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)
|
||||
|
||||
@@ -186,32 +186,6 @@ void Preferences::setAlternatingRowColors(const bool b)
|
||||
setValue(u"Preferences/General/AlternatingRowColors"_s, b);
|
||||
}
|
||||
|
||||
bool Preferences::useTorrentStatesColors() const
|
||||
{
|
||||
return value(u"GUI/TransferList/UseTorrentStatesColors"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setUseTorrentStatesColors(const bool value)
|
||||
{
|
||||
if (value == useTorrentStatesColors())
|
||||
return;
|
||||
|
||||
setValue(u"GUI/TransferList/UseTorrentStatesColors"_s, value);
|
||||
}
|
||||
|
||||
bool Preferences::getProgressBarFollowsTextColor() const
|
||||
{
|
||||
return value(u"GUI/TransferList/ProgressBarFollowsTextColor"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setProgressBarFollowsTextColor(const bool value)
|
||||
{
|
||||
if (value == getProgressBarFollowsTextColor())
|
||||
return;
|
||||
|
||||
setValue(u"GUI/TransferList/ProgressBarFollowsTextColor"_s, value);
|
||||
}
|
||||
|
||||
bool Preferences::getHideZeroValues() const
|
||||
{
|
||||
return value(u"Preferences/General/HideZeroValues"_s, false);
|
||||
@@ -385,19 +359,6 @@ void Preferences::setStatusbarDisplayed(const bool displayed)
|
||||
setValue(u"Preferences/General/StatusbarDisplayed"_s, displayed);
|
||||
}
|
||||
|
||||
bool Preferences::isStatusbarFreeDiskSpaceDisplayed() const
|
||||
{
|
||||
return value(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStatusbarFreeDiskSpaceDisplayed(const bool displayed)
|
||||
{
|
||||
if (displayed == isStatusbarFreeDiskSpaceDisplayed())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, displayed);
|
||||
}
|
||||
|
||||
bool Preferences::isStatusbarExternalIPDisplayed() const
|
||||
{
|
||||
return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false);
|
||||
|
||||
@@ -113,18 +113,12 @@ public:
|
||||
void showSpeedInTitleBar(bool show);
|
||||
bool useAlternatingRowColors() const;
|
||||
void setAlternatingRowColors(bool b);
|
||||
bool useTorrentStatesColors() const;
|
||||
void setUseTorrentStatesColors(bool value);
|
||||
bool getProgressBarFollowsTextColor() const;
|
||||
void setProgressBarFollowsTextColor(bool value);
|
||||
bool getHideZeroValues() const;
|
||||
void setHideZeroValues(bool b);
|
||||
int getHideZeroComboValues() const;
|
||||
void setHideZeroComboValues(int n);
|
||||
bool isStatusbarDisplayed() const;
|
||||
void setStatusbarDisplayed(bool displayed);
|
||||
bool isStatusbarFreeDiskSpaceDisplayed() const;
|
||||
void setStatusbarFreeDiskSpaceDisplayed(bool displayed);
|
||||
bool isStatusbarExternalIPDisplayed() const;
|
||||
void setStatusbarExternalIPDisplayed(bool displayed);
|
||||
bool isToolbarDisplayed() const;
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
|
||||
#include "base/addtorrentmanager.h"
|
||||
#include "base/asyncfilestorage.h"
|
||||
#include "base/bittorrent/addtorrenterror.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
#include "base/global.h"
|
||||
@@ -118,7 +117,7 @@ AutoDownloader::AutoDownloader(IApplication *app)
|
||||
|
||||
m_fileStorage->moveToThread(m_ioThread.get());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater);
|
||||
connect(m_fileStorage, &AsyncFileStorage::failed, this, [](const Path &fileName, const QString &errorString)
|
||||
connect(m_fileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
|
||||
{
|
||||
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
||||
.arg(fileName.toString(), errorString), Log::CRITICAL);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "base/applicationcomponent.h"
|
||||
#include "base/bittorrent/addtorrenterror.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "base/utils/thread.h"
|
||||
@@ -47,11 +48,6 @@ class Application;
|
||||
class AsyncFileStorage;
|
||||
struct ProcessingJob;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
struct AddTorrentError;
|
||||
}
|
||||
|
||||
namespace RSS
|
||||
{
|
||||
class Article;
|
||||
|
||||
@@ -184,10 +184,14 @@ QString computeEpisodeName(const QString &article)
|
||||
for (int i = 1; i <= match.lastCapturedIndex(); ++i)
|
||||
{
|
||||
const QString cap = match.captured(i);
|
||||
|
||||
if (cap.isEmpty())
|
||||
continue;
|
||||
|
||||
ret.append(cap);
|
||||
bool isInt = false;
|
||||
const int x = cap.toInt(&isInt);
|
||||
|
||||
ret.append(isInt ? QString::number(x) : cap);
|
||||
}
|
||||
return ret.join(u'x');
|
||||
}
|
||||
@@ -289,26 +293,20 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
|
||||
if (!matcher.hasMatch())
|
||||
return false;
|
||||
|
||||
const QStringView season {matcher.capturedView(1)};
|
||||
const QList<QStringView> episodes {matcher.capturedView(2).split(u';')};
|
||||
const QString season {matcher.captured(1)};
|
||||
const QStringList episodes {matcher.captured(2).split(u';')};
|
||||
const int seasonOurs {season.toInt()};
|
||||
|
||||
for (QStringView episode : episodes)
|
||||
for (QString episode : episodes)
|
||||
{
|
||||
if (episode.isEmpty())
|
||||
continue;
|
||||
|
||||
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
|
||||
while ((episode.size() > 1) && episode.startsWith(u'0'))
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
episode.slice(1);
|
||||
#else
|
||||
episode = episode.sliced(1);
|
||||
#endif
|
||||
}
|
||||
episode = episode.right(episode.size() - 1);
|
||||
|
||||
if (episode.contains(u'-'))
|
||||
if (episode.indexOf(u'-') != -1)
|
||||
{ // Range detected
|
||||
const QString partialPattern1 {u"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"_s};
|
||||
const QString partialPattern2 {u"\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)"_s};
|
||||
@@ -325,25 +323,24 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
|
||||
|
||||
if (matched)
|
||||
{
|
||||
const int seasonTheirs {matcher.capturedView(1).toInt()};
|
||||
const int episodeTheirs {matcher.capturedView(2).toInt()};
|
||||
const int seasonTheirs {matcher.captured(1).toInt()};
|
||||
const int episodeTheirs {matcher.captured(2).toInt()};
|
||||
|
||||
if (episode.endsWith(u'-'))
|
||||
{ // Infinite range
|
||||
const int episodeOurs {QStringView(episode).chopped(1).toInt()};
|
||||
const int episodeOurs {QStringView(episode).left(episode.size() - 1).toInt()};
|
||||
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{ // Normal range
|
||||
const QList<QStringView> range {episode.split(u'-')};
|
||||
const QStringList range {episode.split(u'-')};
|
||||
Q_ASSERT(range.size() == 2);
|
||||
if (range.first().toInt() > range.last().toInt())
|
||||
continue; // Ignore this subrule completely
|
||||
|
||||
const int episodeOursFirst {range.first().toInt()};
|
||||
const int episodeOursLast {range.last().toInt()};
|
||||
if (episodeOursFirst > episodeOursLast)
|
||||
continue; // Ignore this subrule completely
|
||||
|
||||
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2024 Jonathan Ketchker
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
@@ -56,22 +56,19 @@
|
||||
|
||||
const QString KEY_UID = u"uid"_s;
|
||||
const QString KEY_URL = u"url"_s;
|
||||
const QString KEY_REFRESHINTERVAL = u"refreshInterval"_s;
|
||||
const QString KEY_TITLE = u"title"_s;
|
||||
const QString KEY_LASTBUILDDATE = u"lastBuildDate"_s;
|
||||
const QString KEY_ISLOADING = u"isLoading"_s;
|
||||
const QString KEY_HASERROR = u"hasError"_s;
|
||||
const QString KEY_ARTICLES = u"articles"_s;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace RSS;
|
||||
|
||||
Feed::Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
|
||||
Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *session)
|
||||
: Item(path)
|
||||
, m_session {session}
|
||||
, m_uid {uid}
|
||||
, m_url {url}
|
||||
, m_refreshInterval {refreshInterval}
|
||||
, m_session(session)
|
||||
, m_uid(uid)
|
||||
, m_url(url)
|
||||
{
|
||||
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
|
||||
m_dataFileName = Path(uidHex + u".json");
|
||||
@@ -330,9 +327,9 @@ bool Feed::addArticle(const QVariantHash &articleData)
|
||||
|
||||
// Insertion sort
|
||||
const int maxArticles = m_session->maxArticlesPerFeed();
|
||||
const auto lowerBound = std::lower_bound(m_articlesByDate.cbegin(), m_articlesByDate.cend()
|
||||
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
|
||||
if ((lowerBound - m_articlesByDate.cbegin()) >= maxArticles)
|
||||
const auto lowerBound = std::lower_bound(m_articlesByDate.begin(), m_articlesByDate.end()
|
||||
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
|
||||
if ((lowerBound - m_articlesByDate.begin()) >= maxArticles)
|
||||
return false; // we reach max articles
|
||||
|
||||
auto *article = new Article(this, articleData);
|
||||
@@ -465,20 +462,6 @@ Path Feed::iconPath() const
|
||||
return m_iconPath;
|
||||
}
|
||||
|
||||
std::chrono::seconds Feed::refreshInterval() const
|
||||
{
|
||||
return m_refreshInterval;
|
||||
}
|
||||
|
||||
void Feed::setRefreshInterval(const std::chrono::seconds refreshInterval)
|
||||
{
|
||||
if (refreshInterval == m_refreshInterval)
|
||||
return;
|
||||
|
||||
const std::chrono::seconds oldRefreshInterval = std::exchange(m_refreshInterval, refreshInterval);
|
||||
emit refreshIntervalChanged(oldRefreshInterval);
|
||||
}
|
||||
|
||||
void Feed::setURL(const QString &url)
|
||||
{
|
||||
const QString oldURL = m_url;
|
||||
@@ -491,8 +474,6 @@ QJsonValue Feed::toJsonValue(const bool withData) const
|
||||
QJsonObject jsonObj;
|
||||
jsonObj.insert(KEY_UID, uid().toString());
|
||||
jsonObj.insert(KEY_URL, url());
|
||||
if (refreshInterval() > 0s)
|
||||
jsonObj.insert(KEY_REFRESHINTERVAL, static_cast<qint64>(refreshInterval().count()));
|
||||
|
||||
if (withData)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2024 Jonathan Ketchker
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
@@ -31,8 +31,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QBasicTimer>
|
||||
#include <QHash>
|
||||
@@ -70,7 +68,7 @@ namespace RSS
|
||||
|
||||
friend class Session;
|
||||
|
||||
Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, std::chrono::seconds refreshInterval);
|
||||
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
|
||||
~Feed() override;
|
||||
|
||||
public:
|
||||
@@ -89,9 +87,6 @@ namespace RSS
|
||||
Article *articleByGUID(const QString &guid) const;
|
||||
Path iconPath() const;
|
||||
|
||||
std::chrono::seconds refreshInterval() const;
|
||||
void setRefreshInterval(std::chrono::seconds refreshInterval);
|
||||
|
||||
QJsonValue toJsonValue(bool withData = false) const override;
|
||||
|
||||
signals:
|
||||
@@ -99,7 +94,6 @@ namespace RSS
|
||||
void titleChanged(Feed *feed = nullptr);
|
||||
void stateChanged(Feed *feed = nullptr);
|
||||
void urlChanged(const QString &oldURL);
|
||||
void refreshIntervalChanged(std::chrono::seconds oldRefreshInterval);
|
||||
|
||||
private slots:
|
||||
void handleSessionProcessingEnabledChanged(bool enabled);
|
||||
@@ -129,7 +123,6 @@ namespace RSS
|
||||
Private::FeedSerializer *m_serializer = nullptr;
|
||||
const QUuid m_uid;
|
||||
QString m_url;
|
||||
std::chrono::seconds m_refreshInterval;
|
||||
QString m_title;
|
||||
QString m_lastBuildDate;
|
||||
bool m_hasError = false;
|
||||
|
||||
@@ -97,7 +97,7 @@ QStringList Item::expandPath(const QString &path)
|
||||
int index = 0;
|
||||
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
|
||||
{
|
||||
result << path.first(index);
|
||||
result << path.left(index);
|
||||
++index;
|
||||
}
|
||||
result << path;
|
||||
@@ -108,11 +108,11 @@ QStringList Item::expandPath(const QString &path)
|
||||
QString Item::parentPath(const QString &path)
|
||||
{
|
||||
const int pos = path.lastIndexOf(Item::PathSeparator);
|
||||
return (pos >= 0) ? path.first(pos) : QString();
|
||||
return (pos >= 0) ? path.left(pos) : QString();
|
||||
}
|
||||
|
||||
QString Item::relativeName(const QString &path)
|
||||
{
|
||||
const int pos = path.lastIndexOf(Item::PathSeparator);
|
||||
return (pos >= 0) ? path.sliced(pos + 1) : path;
|
||||
int pos;
|
||||
return ((pos = path.lastIndexOf(Item::PathSeparator)) >= 0 ? path.right(path.size() - (pos + 1)) : path);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2024 Jonathan Ketchker
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
|
||||
*
|
||||
@@ -56,7 +56,6 @@ const QString CONF_FOLDER_NAME = u"rss"_s;
|
||||
const QString DATA_FOLDER_NAME = u"rss/articles"_s;
|
||||
const QString FEEDS_FILE_NAME = u"feeds.json"_s;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace RSS;
|
||||
|
||||
QPointer<Session> Session::m_instance = nullptr;
|
||||
@@ -74,7 +73,7 @@ Session::Session()
|
||||
m_confFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FOLDER_NAME));
|
||||
m_confFileStorage->moveToThread(m_workingThread.get());
|
||||
connect(m_workingThread.get(), &QThread::finished, m_confFileStorage, &AsyncFileStorage::deleteLater);
|
||||
connect(m_confFileStorage, &AsyncFileStorage::failed, this, [](const Path &fileName, const QString &errorString)
|
||||
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
|
||||
{
|
||||
LogMsg(tr("Couldn't save RSS session configuration. File: \"%1\". Error: \"%2\"")
|
||||
.arg(fileName.toString(), errorString), Log::WARNING);
|
||||
@@ -83,7 +82,7 @@ Session::Session()
|
||||
m_dataFileStorage = new AsyncFileStorage(specialFolderLocation(SpecialFolder::Data) / Path(DATA_FOLDER_NAME));
|
||||
m_dataFileStorage->moveToThread(m_workingThread.get());
|
||||
connect(m_workingThread.get(), &QThread::finished, m_dataFileStorage, &AsyncFileStorage::deleteLater);
|
||||
connect(m_dataFileStorage, &AsyncFileStorage::failed, this, [](const Path &fileName, const QString &errorString)
|
||||
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const Path &fileName, const QString &errorString)
|
||||
{
|
||||
LogMsg(tr("Couldn't save RSS session data. File: \"%1\". Error: \"%2\"")
|
||||
.arg(fileName.toString(), errorString), Log::WARNING);
|
||||
@@ -95,10 +94,12 @@ Session::Session()
|
||||
m_workingThread->start();
|
||||
load();
|
||||
|
||||
m_refreshTimer.setSingleShot(true);
|
||||
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
|
||||
if (isProcessingEnabled())
|
||||
{
|
||||
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Remove legacy/corrupted settings
|
||||
// (at least on Windows, QSettings is case-insensitive and it can get
|
||||
@@ -137,36 +138,35 @@ Session *Session::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
nonstd::expected<Folder *, QString> Session::addFolder(const QString &path)
|
||||
nonstd::expected<void, QString> Session::addFolder(const QString &path)
|
||||
{
|
||||
const nonstd::expected<Folder *, QString> result = prepareItemDest(path);
|
||||
if (!result)
|
||||
return nonstd::make_unexpected(result.error());
|
||||
return result.get_unexpected();
|
||||
|
||||
auto *destFolder = result.value();
|
||||
auto *folder = new Folder(path);
|
||||
addItem(folder, destFolder);
|
||||
addItem(new Folder(path), destFolder);
|
||||
store();
|
||||
return folder;
|
||||
return {};
|
||||
}
|
||||
|
||||
nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
|
||||
nonstd::expected<void, QString> Session::addFeed(const QString &url, const QString &path)
|
||||
{
|
||||
if (m_feedsByURL.contains(url))
|
||||
return nonstd::make_unexpected(tr("RSS feed with given URL already exists: %1.").arg(url));
|
||||
|
||||
const nonstd::expected<Folder *, QString> result = prepareItemDest(path);
|
||||
if (!result)
|
||||
return nonstd::make_unexpected(result.error());
|
||||
return result.get_unexpected();
|
||||
|
||||
auto *destFolder = result.value();
|
||||
auto *feed = new Feed(this, generateUID(), url, path, refreshInterval);
|
||||
auto *feed = new Feed(generateUID(), url, path, this);
|
||||
addItem(feed, destFolder);
|
||||
store();
|
||||
if (isProcessingEnabled())
|
||||
refreshFeed(feed, std::chrono::system_clock::now());
|
||||
feed->refresh();
|
||||
|
||||
return feed;
|
||||
return {};
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> Session::setFeedURL(const QString &path, const QString &url)
|
||||
@@ -192,7 +192,7 @@ nonstd::expected<void, QString> Session::setFeedURL(Feed *feed, const QString &u
|
||||
feed->setURL(url);
|
||||
store();
|
||||
if (isProcessingEnabled())
|
||||
refreshFeed(feed, std::chrono::system_clock::now());
|
||||
feed->refresh();
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -214,20 +214,14 @@ nonstd::expected<void, QString> Session::moveItem(Item *item, const QString &des
|
||||
Q_ASSERT(item);
|
||||
Q_ASSERT(item != rootFolder());
|
||||
|
||||
if (item->path() == destPath)
|
||||
return {};
|
||||
|
||||
if (auto *folder = static_cast<Folder *>(item)) // if `item` is a `Folder`
|
||||
{
|
||||
if (destPath.startsWith(folder->path() + Item::PathSeparator))
|
||||
return nonstd::make_unexpected(tr("Can't move a folder into itself or its subfolders."));
|
||||
}
|
||||
|
||||
const nonstd::expected<Folder *, QString> result = prepareItemDest(destPath);
|
||||
if (!result)
|
||||
return nonstd::make_unexpected(result.error());
|
||||
return result.get_unexpected();
|
||||
|
||||
auto *destFolder = result.value();
|
||||
if (static_cast<Item *>(destFolder) == item)
|
||||
return nonstd::make_unexpected(tr("Couldn't move folder into itself."));
|
||||
|
||||
auto *srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
|
||||
if (srcFolder != destFolder)
|
||||
{
|
||||
@@ -320,7 +314,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
QString url = val.toString();
|
||||
if (url.isEmpty())
|
||||
url = key;
|
||||
addFeedToFolder(generateUID(), url, key, folder, 0s);
|
||||
addFeedToFolder(generateUID(), url, key, folder);
|
||||
updated = true;
|
||||
}
|
||||
else if (val.isObject())
|
||||
@@ -360,9 +354,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
updated = true;
|
||||
}
|
||||
|
||||
const auto refreshInterval = std::chrono::seconds(valObj[u"refreshInterval"].toInteger());
|
||||
|
||||
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder, refreshInterval);
|
||||
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -393,14 +385,8 @@ void Session::loadLegacy()
|
||||
uint i = 0;
|
||||
for (QString legacyPath : legacyFeedPaths)
|
||||
{
|
||||
if (legacyPath.startsWith(Item::PathSeparator))
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
legacyPath.slice(1);
|
||||
#else
|
||||
if (Item::PathSeparator == legacyPath[0])
|
||||
legacyPath.remove(0, 1);
|
||||
#endif
|
||||
}
|
||||
const QString parentFolderPath = Item::parentPath(legacyPath);
|
||||
const QString feedUrl = Item::relativeName(legacyPath);
|
||||
|
||||
@@ -418,7 +404,7 @@ void Session::loadLegacy()
|
||||
void Session::store()
|
||||
{
|
||||
m_confFileStorage->store(Path(FEEDS_FILE_NAME)
|
||||
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
|
||||
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
|
||||
}
|
||||
|
||||
nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path)
|
||||
@@ -444,9 +430,9 @@ Folder *Session::addSubfolder(const QString &name, Folder *parentFolder)
|
||||
return folder;
|
||||
}
|
||||
|
||||
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, const std::chrono::seconds refreshInterval)
|
||||
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder)
|
||||
{
|
||||
auto *feed = new Feed(this, uid, url, Item::joinPath(parentFolder->path(), name), refreshInterval);
|
||||
auto *feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
|
||||
addItem(feed, parentFolder);
|
||||
return feed;
|
||||
}
|
||||
@@ -468,25 +454,8 @@ void Session::addItem(Item *item, Folder *destFolder)
|
||||
|
||||
emit feedURLChanged(feed, oldURL);
|
||||
});
|
||||
connect(feed, &Feed::refreshIntervalChanged, this, [this, feed](const std::chrono::seconds oldRefreshInterval)
|
||||
{
|
||||
store();
|
||||
|
||||
std::chrono::system_clock::time_point &nextRefresh = m_refreshTimepoints[feed];
|
||||
if (nextRefresh > std::chrono::system_clock::time_point())
|
||||
nextRefresh += feed->refreshInterval() - oldRefreshInterval;
|
||||
|
||||
if (isProcessingEnabled())
|
||||
{
|
||||
const std::chrono::seconds oldEffectiveRefreshInterval = (oldRefreshInterval > 0s)
|
||||
? oldRefreshInterval : std::chrono::minutes(refreshInterval());
|
||||
if (feed->refreshInterval() < oldEffectiveRefreshInterval)
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
m_feedsByUID[feed->uid()] = feed;
|
||||
m_feedsByURL[feed->url()] = feed;
|
||||
m_refreshTimepoints.emplace(feed, std::chrono::system_clock::time_point());
|
||||
}
|
||||
|
||||
connect(item, &Item::pathChanged, this, &Session::itemPathChanged);
|
||||
@@ -507,9 +476,14 @@ void Session::setProcessingEnabled(const bool enabled)
|
||||
{
|
||||
m_storeProcessingEnabled = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
|
||||
refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_refreshTimer.stop();
|
||||
}
|
||||
|
||||
emit processingStateChanged(enabled);
|
||||
}
|
||||
@@ -580,7 +554,6 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
|
||||
{
|
||||
m_feedsByUID.remove(feed->uid());
|
||||
m_feedsByURL.remove(feed->url());
|
||||
m_refreshTimepoints.remove(feed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,28 +592,6 @@ void Session::setMaxArticlesPerFeed(const int n)
|
||||
|
||||
void Session::refresh()
|
||||
{
|
||||
const auto currentTimepoint = std::chrono::system_clock::now();
|
||||
std::chrono::seconds nextRefreshInterval = 0s;
|
||||
for (auto it = m_refreshTimepoints.begin(); it != m_refreshTimepoints.end(); ++it)
|
||||
{
|
||||
Feed *feed = it.key();
|
||||
std::chrono::system_clock::time_point &timepoint = it.value();
|
||||
|
||||
if (timepoint <= currentTimepoint)
|
||||
timepoint = refreshFeed(feed, currentTimepoint);
|
||||
|
||||
const auto interval = std::chrono::duration_cast<std::chrono::seconds>(timepoint - currentTimepoint);
|
||||
if ((interval < nextRefreshInterval) || (nextRefreshInterval == 0s))
|
||||
nextRefreshInterval = interval;
|
||||
}
|
||||
|
||||
m_refreshTimer.start(nextRefreshInterval);
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point Session::refreshFeed(Feed *feed, const std::chrono::system_clock::time_point ¤tTimepoint)
|
||||
{
|
||||
feed->refresh();
|
||||
const std::chrono::seconds feedRefreshInterval = feed->refreshInterval();
|
||||
const std::chrono::seconds effectiveRefreshInterval = (feedRefreshInterval > 0s) ? feedRefreshInterval : std::chrono::minutes(refreshInterval());
|
||||
return currentTimepoint + effectiveRefreshInterval;
|
||||
// NOTE: Should we allow manually refreshing for disabled session?
|
||||
rootFolder()->refresh();
|
||||
}
|
||||
|
||||