Compare commits

..

2 commits

Author SHA1 Message Date
sledgehammer999
bc7d5c1f8f
Bump to 5.1.0rc1 2025-02-11 02:01:34 +02:00
sledgehammer999
8aabef423c
Create new resources for this branch for Transifex 2025-02-11 01:59:07 +02:00
457 changed files with 115478 additions and 114668 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: "https://www.qbittorrent.org/donate"
custom: "https://www.qbittorrent.org/donate.php"

View file

@ -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)))" \

View file

@ -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"
@ -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

View file

@ -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: |
@ -89,10 +85,6 @@ jobs:
--max-line-length=1000 \
--statistics \
$PY_FILES
isort \
--check \
--diff \
$PY_FILES
- name: Build code (search engine)
run: |

View file

@ -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"
@ -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 \
@ -141,15 +138,16 @@ jobs:
- name: Install AppImage
run: |
sudo apt install libfuse2
curl \
-L \
-Z \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x \
linuxdeploy-x86_64.AppImage \
linuxdeploy-plugin-qt-x86_64.AppImage \
linuxdeploy-static-x86_64.AppImage \
linuxdeploy-plugin-qt-static-x86_64.AppImage \
linuxdeploy-plugin-appimage-x86_64.AppImage
- name: Prepare files for AppImage
@ -162,13 +160,12 @@ 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
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
NO_APPSTREAM=1 \
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage
- name: Upload build artifacts
uses: actions/upload-artifact@v4

View file

@ -34,12 +34,7 @@ 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
run: npm run lint

View file

@ -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 `

View file

@ -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"
@ -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" \

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -1,68 +0,0 @@
# WebAPI Changelog
## 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`
## 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

View file

@ -47,9 +47,6 @@ find_package(Boost ${minBoostVersion} REQUIRED)
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
find_package(ZLIB ${minZlibVersion} REQUIRED)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS Core Network Sql Xml LinguistTools)
if (Qt6_FOUND AND (Qt6_VERSION VERSION_GREATER_EQUAL 6.10))
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS CorePrivate)
endif()
if (DBUS)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS DBus)
set_package_properties(Qt6DBus PROPERTIES

View file

@ -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>

4
dist/mac/Info.plist vendored
View file

@ -55,7 +55,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.2.0</string>
<string>5.1.0</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
@ -67,7 +67,7 @@
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2006-2025 The qBittorrent project</string>
<string>Copyright © 2006-2024 The qBittorrent project</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

Binary file not shown.

Binary file not shown.

View file

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 750 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 750 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

View file

@ -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 &amp; 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>

View file

@ -105,7 +105,7 @@ GenericName[ka]=BitTorrent კლიენტი
Comment[ka]= BitTorrent-
Name[ka]=qBittorrent
GenericName[ko]=BitTorrent
Comment[ko]=BitTorrent
Comment[ko]=BitTorrent
Name[ko]=qBittorrent
GenericName[lt]=BitTorrent klientas
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle

View file

@ -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.0~rc1" date="2025-02-11"/>
</releases>
</component>

View file

@ -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.0"
; Option that controls the installer's window name
; If set, its value will be used like this:
@ -86,7 +86,7 @@ OutFile "qbittorrent_${QBT_INSTALLER_FILENAME}_setup.exe"
;Installer Version Information
VIAddVersionKey "ProductName" "qBittorrent"
VIAddVersionKey "CompanyName" "The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2025 The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2024 The qBittorrent project"
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
VIAddVersionKey "FileVersion" "${QBT_VERSION}"
@ -111,8 +111,7 @@ RequestExecutionLevel user
!define MUI_HEADERIMAGE
!define MUI_COMPONENTSPAGE_NODESC
;!define MUI_ICON "qbittorrent.ico"
!define MUI_LICENSEPAGE_BUTTON $(^NextBtn)
!define MUI_LICENSEPAGE_TEXT_BOTTOM "$_CLICK"
!define MUI_LICENSEPAGE_CHECKBOX
!define MUI_LANGDLL_ALLLANGUAGES
;--------------------------------

View file

@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
;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_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -86,19 +86,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
)
# Generate lproj folders for the translations
foreach(TS_FILE IN LISTS QBT_TS_FILES)
string(FIND "${TS_FILE}" "_" POS)
math(EXPR START "${POS} + 1")
string(SUBSTRING "${TS_FILE}" ${START} -1 LPROJ_FOLDER)
string(REPLACE ".ts" ".lproj" LPROJ_FOLDER "${LPROJ_FOLDER}")
# @ is not valid as a language code for a lproj folder on MacOS
string(REPLACE "@" "-" LPROJ_FOLDER "${LPROJ_FOLDER}")
add_custom_command(TARGET qbt_app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:qbt_app>/../Resources/${LPROJ_FOLDER}"
)
endforeach()
# provide variables for substitution in dist/mac/Info.plist
get_target_property(EXECUTABLE_NAME qbt_app OUTPUT_NAME)
# This variable name should be changed once qmake is no longer used. Refer to the discussion in PR #14813

View file

@ -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
*
* This program is free software; you can redistribute it and/or
@ -124,28 +124,6 @@ namespace
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
const QString PARAM_CATEGORY = u"@category"_s;
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
const QString PARAM_SAVEPATH = u"@savePath"_s;
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
{
return paramName + u'=' + paramValue;
}
std::pair<QStringView, QStringView> parseParam(const QStringView param)
{
const qsizetype sepIndex = param.indexOf(u'=');
if (sepIndex >= 0)
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
return {param, {}};
}
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
@ -160,86 +138,85 @@ namespace
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
result.append(u"@savePath=" + addTorrentParams.savePath.data());
if (addTorrentParams.addStopped.has_value())
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
if (addTorrentParams.skipChecking)
result.append(PARAM_SKIPCHECKING);
result.append(u"@skipChecking"_s);
if (!addTorrentParams.category.isEmpty())
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
result.append(u"@category=" + addTorrentParams.category);
if (addTorrentParams.sequential)
result.append(PARAM_SEQUENTIAL);
result.append(u"@sequential"_s);
if (addTorrentParams.firstLastPiecePriority)
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
result.append(u"@firstLastPiecePriority"_s);
if (params.skipDialog.has_value())
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QStringView str)
QBtCommandLineParameters parseParams(const QString &str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
const auto [paramName, paramValue] = parseParam(param);
// Process strings indicating options specified by the user.
if (paramName == PARAM_SAVEPATH)
if (param.startsWith(u"@savePath="))
{
addTorrentParams.savePath = Path(paramValue.toString());
addTorrentParams.savePath = Path(param.mid(10));
continue;
}
if (paramName == PARAM_ADDSTOPPED)
if (param.startsWith(u"@addStopped="))
{
addTorrentParams.addStopped = (paramValue.toInt() != 0);
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (paramName == PARAM_SKIPCHECKING)
if (param == u"@skipChecking")
{
addTorrentParams.skipChecking = true;
continue;
}
if (paramName == PARAM_CATEGORY)
if (param.startsWith(u"@category="))
{
addTorrentParams.category = paramValue.toString();
addTorrentParams.category = param.mid(10);
continue;
}
if (paramName == PARAM_SEQUENTIAL)
if (param == u"@sequential")
{
addTorrentParams.sequential = true;
continue;
}
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
if (param == u"@firstLastPiecePriority")
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (paramName == PARAM_SKIPDIALOG)
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipDialog = (paramValue.toInt() != 0);
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param.toString());
parsedParams.torrentSources.append(param);
}
return parsedParams;
@ -410,7 +387,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 +552,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 +636,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 +645,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 +814,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
@ -930,10 +897,10 @@ int Application::exec()
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
});
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
, [this](const QString &source, const BitTorrent::AddTorrentError &reason)
, [this](const QString &source, const QString &reason)
{
m_desktopIntegration->showNotification(tr("Add torrent failed")
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason.message));
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
@ -1205,7 +1172,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;

View file

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

View file

@ -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()

View file

@ -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())

View file

@ -6,7 +6,6 @@ add_library(qbt_base STATIC
applicationcomponent.h
asyncfilestorage.h
bittorrent/abstractfilestorage.h
bittorrent/addtorrenterror.h
bittorrent/addtorrentparams.h
bittorrent/announcetimepoint.h
bittorrent/bandwidthscheduler.h
@ -55,7 +54,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 +159,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

View file

@ -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"
@ -141,7 +140,7 @@ void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
}
}
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason)
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
{
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
{
@ -155,7 +154,7 @@ void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &in
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
{
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::Other, reason});
emit addTorrentFailed(source, reason);
}
void AddTorrentManager::handleDuplicateTorrent(const QString &source
@ -186,9 +185,9 @@ 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));
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, 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, message);
}
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)

View file

@ -44,7 +44,6 @@ namespace BitTorrent
class Session;
class Torrent;
class TorrentDescriptor;
struct AddTorrentError;
}
namespace Net
@ -67,7 +66,7 @@ public:
signals:
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
void addTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &reason);
void addTorrentFailed(const QString &source, const QString &reason);
protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
@ -80,7 +79,7 @@ protected:
private:
void onDownloadFinished(const Net::DownloadResult &result);
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);

View file

@ -1,49 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QMetaType>
#include <QString>
namespace BitTorrent
{
struct AddTorrentError
{
enum Kind
{
DuplicateTorrent,
Other
};
Kind kind = Other;
QString message;
};
}
Q_DECLARE_METATYPE(BitTorrent::AddTorrentError)

View file

@ -147,7 +147,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
const Path torrentFilePath = path() / Path(idString + u".torrent");
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit();
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, -1);
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, torrentSizeLimit);
if (!resumeDataReadResult)
return nonstd::make_unexpected(resumeDataReadResult.error().message);
@ -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);
@ -300,8 +290,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (!metadata.isEmpty())
{
@ -332,8 +320,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
p.save_path = Profile::instance()->fromPortablePath(
Path(fromLTString(p.save_path))).toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)
@ -352,9 +338,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 +424,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;

View file

@ -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;

View file

@ -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)

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-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
@ -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);
@ -218,6 +217,80 @@ namespace
{
return u"%1 %2"_s.arg(quoted(column.name), definition);
}
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
namespace BitTorrent
@ -232,7 +305,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 +401,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 +535,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 +652,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());
@ -619,91 +688,6 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
}
LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &query) const
{
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())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
: QThread(parent)
, m_path {dbPath}
@ -775,9 +759,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 +787,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 +824,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 +889,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);

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2022 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
@ -31,10 +31,9 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QSqlQuery;
namespace BitTorrent
{
class DBResumeDataStorage final : public ResumeDataStorage
@ -49,7 +48,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;
@ -59,7 +58,6 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
class Worker;
Worker *m_asyncWorker = nullptr;

View file

@ -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);
}

View file

@ -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);
};

View file

@ -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;

View file

@ -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:

View file

@ -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;

View file

@ -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

View file

@ -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});
}

View file

@ -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;

View file

@ -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
@ -34,7 +34,6 @@
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrenterror.h"
#include "addtorrentparams.h"
#include "categoryoptions.h"
#include "sharelimitaction.h"
@ -422,8 +421,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,17 +479,16 @@ 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);
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
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 +519,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);
};
}

File diff suppressed because it is too large Load diff

View file

@ -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 &currentHandle, 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 &params);
void saveResumeData();
void saveTorrentsQueue();
@ -704,7 +683,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 +804,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 +850,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();

View file

@ -29,8 +29,6 @@
#include "torrent.h"
#include <limits>
#include <QHash>
#include "infohash.h"
@ -53,7 +51,9 @@ namespace BitTorrent
const int Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME = -2;
const int Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT = -1;
const qreal Torrent::MAX_RATIO = std::numeric_limits<qreal>::infinity();
const qreal Torrent::MAX_RATIO = 9999;
const int Torrent::MAX_SEEDING_TIME = 525600;
const int Torrent::MAX_INACTIVE_SEEDING_TIME = 525600;
TorrentID Torrent::id() const
{

View file

@ -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;
@ -134,6 +132,8 @@ namespace BitTorrent
static const int NO_INACTIVE_SEEDING_TIME_LIMIT;
static const qreal MAX_RATIO;
static const int MAX_SEEDING_TIME;
static const int MAX_INACTIVE_SEEDING_TIME;
using TorrentContentHandler::TorrentContentHandler;
@ -144,7 +144,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 +275,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 +325,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;

View file

@ -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;

View file

@ -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

View file

@ -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)}
{

View file

@ -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 &ltAddTorrentParams() const;

View file

@ -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 &params)
: 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;
@ -1534,8 +1549,7 @@ qreal TorrentImpl::realRatio() const
const qreal ratio = upload / static_cast<qreal>(download);
Q_ASSERT(ratio >= 0);
return ratio;
return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
}
int TorrentImpl::uploadPayloadRate() const
@ -1601,20 +1615,18 @@ bool TorrentImpl::setCategory(const QString &category)
if (!category.isEmpty() && !m_session->categories().contains(category))
return false;
if (m_session->isDisableAutoTMMWhenCategoryChanged())
{
// This should be done before changing the category name
// to prevent the torrent from being moved at the path of new category.
setAutoTMMEnabled(false);
}
const QString oldCategory = m_category;
m_category = category;
deferredRequestResumeData();
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM)
adjustStorageLocation();
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
adjustStorageLocation();
else
setAutoTMMEnabled(false);
}
}
return true;
@ -1737,6 +1749,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 +1865,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 +1874,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 +1903,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 +2060,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 +2103,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 +2126,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 +2195,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 &params)
{
{
decltype(params.have_pieces) havePieces;
@ -2198,7 +2227,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 +2243,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 +2251,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 +2266,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 +2302,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 +2356,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 +2371,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 +2404,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 +2431,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 +2459,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 +2557,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())
@ -2600,6 +2710,8 @@ void TorrentImpl::setRatioLimit(qreal limit)
{
if (limit < USE_GLOBAL_RATIO)
limit = NO_RATIO_LIMIT;
else if (limit > MAX_RATIO)
limit = MAX_RATIO;
if (m_ratioLimit != limit)
{
@ -2613,6 +2725,8 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
{
if (limit < USE_GLOBAL_SEEDING_TIME)
limit = NO_SEEDING_TIME_LIMIT;
else if (limit > MAX_SEEDING_TIME)
limit = MAX_SEEDING_TIME;
if (m_seedingTimeLimit != limit)
{
@ -2626,6 +2740,8 @@ void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
{
if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
else if (limit > MAX_INACTIVE_SEEDING_TIME)
limit = MAX_SEEDING_TIME;
if (m_inactiveSeedingTimeLimit != limit)
{
@ -2766,9 +2882,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)
{
@ -2803,9 +2928,9 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
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 +2945,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 +2965,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 +2982,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 +3007,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 +3047,8 @@ QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
catch (const std::exception &) {}
return {};
});
}
, std::move(resultHandler));
}
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
@ -2929,7 +3059,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 +3088,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);
});
});
}

View file

@ -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 &params);
~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 &params);
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;

View file

@ -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);

View file

@ -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 {};

View file

@ -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;

View file

@ -69,8 +69,8 @@ namespace
return false;
}
const QString name = line.first(i).trimmed().toString().toLower();
const QString value = line.sliced(i + 1).trimmed().toString();
const QString name = line.left(i).trimmed().toString().toLower();
const QString value = line.mid(i + 1).trimmed().toString();
out[name] = value;
return true;
@ -204,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
@ -221,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('+', ' ');
@ -270,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!";
@ -279,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";
@ -310,8 +310,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
return false;
}
const QString headers = QString::fromLatin1(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<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
@ -328,8 +328,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
if (idx < 0)
continue;
const QString name = directive.first(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.sliced(idx + 1).trimmed()).toString();
const QString name = directive.left(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
headersMap[name] = value;
}
}

View file

@ -30,10 +30,8 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QHostAddress>
#include <QList>
#include <QMap>
#include <QString>
#include "base/global.h"

View file

@ -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)

View file

@ -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:

View file

@ -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)

View file

@ -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);

View file

@ -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)

View file

@ -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);
@ -2093,19 +2054,6 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
}
bool Preferences::isAddNewTorrentDialogAttached() const
{
return value(u"AddNewTorrentDialog/Attached"_s, false);
}
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
{
if (attached == isAddNewTorrentDialogAttached())
return;
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
}
void Preferences::apply()
{
if (SettingsStorage::instance()->save())

View file

@ -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;
@ -439,8 +433,6 @@ public:
void setAddNewTorrentDialogTopLevel(bool value);
int addNewTorrentDialogSavePathHistoryLength() const;
void setAddNewTorrentDialogSavePathHistoryLength(int value);
bool isAddNewTorrentDialogAttached() const;
void setAddNewTorrentDialogAttached(bool attached);
public slots:
void setStatusFilterState(bool checked);

View file

@ -48,16 +48,16 @@ const QString Article::KeyIsRead = u"isRead"_s;
Article::Article(Feed *feed, const QVariantHash &varHash)
: QObject(feed)
, m_feed {feed}
, m_guid {varHash.value(KeyId).toString()}
, m_date {varHash.value(KeyDate).toDateTime()}
, m_title {varHash.value(KeyTitle).toString()}
, m_author {varHash.value(KeyAuthor).toString()}
, m_description {varHash.value(KeyDescription).toString()}
, m_torrentURL {varHash.value(KeyTorrentURL).toString()}
, m_link {varHash.value(KeyLink).toString()}
, m_isRead {varHash.value(KeyIsRead, false).toBool()}
, m_data {varHash}
, m_feed(feed)
, m_guid(varHash.value(KeyId).toString())
, m_date(varHash.value(KeyDate).toDateTime())
, m_title(varHash.value(KeyTitle).toString())
, m_author(varHash.value(KeyAuthor).toString())
, m_description(varHash.value(KeyDescription).toString())
, m_torrentURL(varHash.value(KeyTorrentURL).toString())
, m_link(varHash.value(KeyLink).toString())
, m_isRead(varHash.value(KeyIsRead, false).toBool())
, m_data(varHash)
{
}

View file

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

View file

@ -47,11 +47,6 @@ class Application;
class AsyncFileStorage;
struct ProcessingJob;
namespace BitTorrent
{
struct AddTorrentError;
}
namespace RSS
{
class Article;
@ -116,7 +111,7 @@ namespace RSS
private slots:
void process();
void handleTorrentAdded(const QString &source);
void handleAddTorrentFailed(const QString &url, const BitTorrent::AddTorrentError &error);
void handleAddTorrentFailed(const QString &url);
void handleNewArticle(const Article *article);
void handleFeedURLChanged(Feed *feed, const QString &oldURL);

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more