Compare commits

..

53 Commits

Author SHA1 Message Date
sledgehammer999
ea9f3800ce Bump to 5.1.1 2025-06-23 00:40:55 +03:00
sledgehammer999
af14584772 Update Changelog 2025-06-23 00:37:53 +03:00
sledgehammer999
7d51524251 Sync translations from Transifex and run lupdate 2025-06-22 23:25:18 +03:00
Vladimir Golovnev
7a9aac79f9 Backport changes to v5.1.x branch
PR #22591.
2025-06-20 19:16:30 +03:00
Vladimir Golovnev
085ae0d1c4 Don't limit the size of read "resume data"
PR #22825.
2025-06-08 18:39:58 +03:00
tehcneko
f748a682ca WebUI: Fix path autofill in set location and new category
Attach `PathAutofill` after `DOMContentLoaded`.
Also removed pathAutofill.js in newfolder.html since it's meant for new RSS folder name.

PR #22773.
2025-05-29 21:37:07 +03:00
Chocobo1
df987cc954 NSIS: revise license page
The GPL doesn't need to be agreed with and therefore remove 'I accept agreement' checkbox and
adjust related UI elements.

Closes #22660.
PR #22775.
2025-05-27 11:22:40 +03:00
bolshoytoster
535fc42747 WebUI: Fix memory leak
See #22734, there is a memory leak in the MooTools .destroy(), this replaces all uses of that with the browser native .remove().

This also overrides the MooTools Document.id function, which is used by $(id). The original function always allocates an ID to elements it selects, the override doesn't, and is also a little more efficient.

Closes #22734.
PR  #22754.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2025-05-26 15:56:37 +03:00
Vladimir Golovnev
1da31bc2e1 RSS: Mark matched article as "read" if refers to duplicate torrent
PR #22477.
2025-05-26 15:47:09 +03:00
Vladimir Golovnev
9515ca59f2 Improve add torrent error handling
PR #22468.
2025-05-26 15:46:17 +03:00
Chocobo1
eaf9017aa4 WebUI: fix wrong replacement sequence
Only IPv6 addresses may have a 'zone index' and therefore it should be replaced last for the
result to be correct.

PR #22724.
2025-05-18 12:41:19 +03:00
Chocobo1
f51ad39ad9 Add fallback for random number generator
`getrandom()` is available since Linux 3.17 (2014/10/05) yet there are older devices that don't
meet this requirement.

Closes #22691.
PR #22723.
2025-05-18 12:40:49 +03:00
Atk
9133b16431 WebUI: Make multi-rename search & replace fields use a monospace font
Before the release of 5.1.0 I believe these two fields both used monospace fonts and also had an increased font size, maybe 14px (I couldn't find in blame where/how this regressed). Ideally, I'd like to bring this back, as it makes elaborate regexes easier to grok. This PR currently just tells the browser to use its monospace font, but I could also add a similar in-line style for font size. I just don't know if this is the best way to solve this problem; if there's a better place for this sort of styling to happen let me know

PR #22719.
2025-05-18 12:40:24 +03:00
KanishkaHalder1771
909a3eb44e Update help message for Windows systems
For windows environment the `--help` output will show :
```
set QBT_NO_SPLASH=1
C:\Program Files\qBittorrent\qbittorrent.exe
```
instead of
```
QBT_NO_SPLASH=1 C:\Program Files\qBittorrent\qbittorrent.exe
```

Fixes #22662.
PR #22695.
2025-05-18 12:39:53 +03:00
Chocobo1
778aa64c54 WebUI: add versioning to local preferences
And provide migration path for changing preferences.

Fixes #22639.
PR #22677.
2025-05-14 10:41:17 +03:00
Vladimir Golovnev
7049f80a01 Fix compilation with Qt 6.6.0
PR #22678.
2025-05-12 12:23:21 +03:00
dezza
87b90b7fd7 WebUI: Remove unselectable from General tab
Making General-tab text `unselectable` is not an improvement.

It begs to add a new `Copy -> Save path` feature, because using `Set location` to copy save path (*which requires a request*) is not faster than simply copying it from the `General` tab by double-left clicking and pressing `CTRL+C`.

I don't see a reason why its necessary to software-restrict people from copying details from the `General`-tab - there are several reasons why you would - incl. the above mentioned usecase for quickly copying save-path, but other than that its counterproductive to limit people from copying the details displayed.

PR #22663.
2025-05-10 11:58:58 +03:00
Vladimir Golovnev
b3690494ab Fix ratio handling
PR #22638.
2025-05-01 21:17:07 +03:00
Vladimir Golovnev
f4e6b515c2 Remove dubious seeding time max value
PR #22624.
2025-05-01 21:16:17 +03:00
Isak05
a721540e6c Fix preview not opening on Wayland
Deferring the opening of the preview slightly gives the preview select
dialog time to close and for focus to shift back to the main window.

PR #22608.
Closes #22607.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
2025-05-01 21:09:45 +03:00
Vladimir Golovnev
3fd05d001f Fix appearance of search history length spinbox
PR #22605.
2025-05-01 21:09:44 +03:00
Vladimir Golovnev
f04b114b64 Don't interpret wildcard pattern as filepath globbing
PR #22590.
Closes #22583.
2025-05-01 21:09:44 +03:00
sledgehammer999
da87be2b12 Bump to 5.1.0 2025-04-27 11:53:39 +03:00
sledgehammer999
891265b390 Update Changelog 2025-04-27 11:53:25 +03:00
sledgehammer999
f46e44d3ed Sync translations from Transifex and run lupdate 2025-04-27 11:52:43 +03:00
sledgehammer999
a4094a440d Bump copyright year 2025-04-20 23:26:52 +03:00
sledgehammer999
46c3da21e1 Sync translations from Transifex and run lupdate 2025-04-20 23:23:23 +03:00
Vladimir Golovnev
2f06ea2587 Backport changes to v5.1.x branch
PR #22490.
2025-04-17 20:54:03 +03:00
Vladimir Golovnev
cfbf6b73ff Prevent crash due to corrupted resume data
PR #22569.
Closes #22540.
2025-04-17 11:17:19 +03:00
Vladimir Golovnev
c687a7d0d3 Fix the torrent relocates files when switching to "manual" mode
PR #22564.
Closes #22283.
Closes #22546.
2025-04-16 10:24:34 +03:00
Vladimir Golovnev
009cc71f9b Explicitly reject opened Add torrent dialogs when exiting app
PR #22535.
Closes #19933.
Supercedes #22533.
2025-04-14 09:53:07 +03:00
Chocobo1
de1cf208ce WebUI: avoid saving invalid size
Don't save the wrong size when the tab is collapsed.
Reported in: https://github.com/qbittorrent/qBittorrent/pull/21215/files#r1966052959

PR #22537.
2025-04-12 15:13:47 +03:00
skomerko
5f49472fa4 WebUI: Set status filter to 'All' if selected filter is no longer visible
Fixup for #21145

To reproduce:
1. Select status filter with 0 torrents
2. Enable 'Auto hide zero status filters' and save settings. Hidden filter is still selected:

PR #22487.
2025-04-12 07:12:23 +03:00
skomerko
2076302170 WebUI: Show 'Edit tracker URL...' only when one tracker is selected
We can only edit one URL through the dialog, so there's no point in showing this context option when more than one tracker is selected in trackers table.

PR #22311.
2025-04-12 07:11:41 +03:00
skomerko
2a33e187eb WebUI: Update sort icon after changing column order
This PR fixes a bug where the sort icon did not update correctly after reordering columns.

Steps to reproduce:
1. Sort a column
2. Move it to a different position
3. The sort icon remains in its original location

PR #22299.
2025-04-12 07:10:59 +03:00
FredBill1
00149e03c0 Migrate socks.py from SocksiPy to PySocks 1.7.1
Migrate `socks.py` from SocksiPy 1.01 to [PySocks 1.7.1](c2fa43cbe1/socks.py), allowing python 3+ compatibility, [details](https://github.com/qbittorrent/qBittorrent/issues/16447#issuecomment-2776894026).

The content of the `socks.py` is entirely copied from the [PySocks repository](c2fa43cbe1/socks.py), the only modification is the license header at the top of the file and trimming trail whitespaces.

Closes #16447.
PR #22507.
2025-04-09 12:57:05 +03:00
Chocobo1
57d529c17a WebUI: fix preferences not applied in magnet handler
Thanks for the diagnosis in this [post](https://github.com/qbittorrent/qBittorrent/issues/22495#issue-2958553624).

Closes #21486.
Closes #22495.
PR #22504.
2025-04-05 08:58:38 +03:00
Vladimir Golovnev
d492fcf29a Add option to enable previous Add new torrent dialog behavior
Some people are still unhappy with "standalone window mode" of "Add new torrent dialog" so just provide them with an option to use old "modal dialog mode" in all the current qBittorrent branches.

PR #22492 (based on original PR #19874).
2025-03-31 09:19:03 +03:00
Chocobo1
d0caa35b39 WebUI: fix Tag counter counting wrong
Related: 73e9116d21 (r2014898781)

PR #22480.
2025-03-29 16:02:47 +03:00
Vladimir Golovnev
ec7a00af92 Restore ability to use server-side translation by custom WebUI
PR #20968.
2025-03-28 09:08:56 +03:00
Vladimir Golovnev
76a3aba7e0 Backport changes to v5.1.x branch
PR #22268.
2025-03-16 10:39:31 +03:00
Chocobo1
7003ac3f4d WebUI v5.1 fixes
PR #22282.
2025-03-15 14:52:48 +03:00
skomerko
964be0fa1c WebUI: Maintain row highlight after rearranging table columns
This PR fixes a bug where row highlight effect would be lost after reordering columns.

PR #22339.
2025-03-15 12:47:11 +03:00
skomerko
c1defceccf WebUI: Fix bug where the 'Tracker editing' dialog displays incorrect data
In Trackers table, moving the 'URL' column from its default (2) position caused the 'Tracker editing' dialog to display incorrect data.
Steps to reproduce:
1. Move 'URL' column in Trackers table to any position from default
2. Choose tracker URL and click 'Edit tracker URL'

PR #22338.
2025-03-15 12:46:17 +03:00
Vladimir Golovnev
260394623d Add missing includes
PR #22362.
2025-03-05 09:07:47 +03:00
Vladimir Golovnev
478c2d5b12 Don't miss to declare some of the color IDs
PR #22330.
Closes #22326.
2025-02-25 18:57:22 +03:00
Vladimir Golovnev
49cfbd9a49 Improve command line parameters serialization
PR #22319.
Closes #22306.
2025-02-25 09:12:26 +03:00
Luke Memet
d028f46fab Fix shift-click selection on macOS
PR #22284.
Closes #16818.
2025-02-19 13:53:49 +03:00
Daniel Nylander
57b24a200e NSIS: Update Swedish translation
PR #22046.
2025-02-19 13:49:25 +03:00
Chocobo1
269dfe87e0 GHA CI: fix AppImage building
Upstream now defaults to static runtime and the previous URL is invalid now.
Upstream commits:
* c28054bab6
* ce5291e259

Also fuse2 is not needed now as stated on:
https://github.com/AppImage/type2-runtime?tab=readme-ov-file#type2-runtime-

PR #22286.
2025-02-16 08:22:26 +03:00
Vladimir Golovnev
6a1c465d85 WebAPI: Don't trim string parameters
PR #22266.
Closes #19485.
Closes #22254.
2025-02-12 09:35:29 +03:00
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
461 changed files with 79615 additions and 98729 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

@@ -16,7 +16,7 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -36,7 +36,7 @@ jobs:
curl \ curl \
-L \ -L \
-o "${{ runner.temp }}/pandoc.tar.gz" \ -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 }}/.." tar -xf "${{ runner.temp }}/pandoc.tar.gz" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}" mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}"
# run pandoc # run pandoc
@@ -52,13 +52,13 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
pip install zizmor pip install zizmor
IGNORE_RULEID='(.ruleId != "zizmor/template-injection") IGNORE_RULEID='(.ruleId != "template-injection")
and (.ruleId != "zizmor/unpinned-uses")' and (.ruleId != "unpinned-uses")'
IGNORE_ID='(.id != "zizmor/template-injection") IGNORE_ID='(.id != "template-injection")
and (.id != "zizmor/unpinned-uses")' and (.id != "unpinned-uses")'
zizmor \ zizmor \
--format sarif \ --format sarif \
--persona auditor \ --pedantic \
./ \ ./ \
| jq "(.runs[].results |= map(select($IGNORE_RULEID))) | jq "(.runs[].results |= map(select($IGNORE_RULEID)))
| (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \ | (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \

View File

@@ -20,7 +20,7 @@ jobs:
matrix: matrix:
libt_version: ["2.0.11", "1.2.20"] libt_version: ["2.0.11", "1.2.20"]
qbt_gui: ["GUI=ON", "GUI=OFF"] qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.9.1"] qt_version: ["6.7.0"]
env: env:
boost_path: "${{ github.workspace }}/../boost" boost_path: "${{ github.workspace }}/../boost"
@@ -28,7 +28,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -43,8 +43,8 @@ jobs:
command: | command: |
brew update > /dev/null brew update > /dev/null
brew install \ brew install \
cmake ninja \
openssl@3 zlib openssl@3 zlib
# preinstalled on the image: cmake ninja
- name: Setup ccache - name: Setup ccache
uses: Chocobo1/setup-ccache-action@v1 uses: Chocobo1/setup-ccache-action@v1
@@ -52,7 +52,7 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }} store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false update_packager_index: false
ccache_options: | ccache_options: |
max_size=1G max_size=2G
- name: Install boost - name: Install boost
env: env:
@@ -70,9 +70,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?" tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}" mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -98,7 +95,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \ -DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF -Ddeprecated-functions=OFF
cmake --build build cmake --build build
sudo cmake --install build sudo cmake --install build
@@ -112,7 +109,7 @@ jobs:
-G "Ninja" \ -G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-DTESTING=ON \ -DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \ -DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }} -D${{ matrix.qbt_gui }}
@@ -122,24 +119,17 @@ jobs:
- name: Prepare build artifacts - name: Prepare build artifacts
run: | run: |
# create .dmg
appName="qbittorrent" appName="qbittorrent"
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
appName="qbittorrent-nox" appName="qbittorrent-nox"
fi fi
# package
pushd build 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 PACKAGE_RETRY=0
while [ "$PACKAGE_RETRY" -lt "3" ]; do 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 break
fi fi
sleep 5 sleep 5

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -25,7 +25,7 @@ jobs:
python-version: '3' # use default version python-version: '3' # use default version
- name: Install tools (auxiliary scripts) - name: Install tools (auxiliary scripts)
run: pip install bandit isort pycodestyle pyflakes run: pip install bandit pycodestyle pyflakes
- name: Gather files (auxiliary scripts) - name: Gather files (auxiliary scripts)
run: | run: |
@@ -44,10 +44,6 @@ jobs:
--max-line-length=1000 \ --max-line-length=1000 \
--statistics \ --statistics \
$PY_FILES $PY_FILES
isort \
--check \
--diff \
$PY_FILES
- name: Build code (auxiliary scripts) - name: Build code (auxiliary scripts)
run: | run: |
@@ -59,7 +55,7 @@ jobs:
python-version: '3.9' python-version: '3.9'
- name: Install tools (search engine) - 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) - name: Gather files (search engine)
run: | run: |
@@ -69,12 +65,9 @@ jobs:
- name: Check typings (search engine) - name: Check typings (search engine)
run: | run: |
curl \
-L \
-o src/searchengine/nova3/socks.pyi "https://github.com/python/typeshed/raw/refs/heads/main/stubs/PySocks/socks.pyi"
MYPYPATH="src/searchengine/nova3" \ MYPYPATH="src/searchengine/nova3" \
mypy \ mypy \
--explicit-package-bases \ --follow-imports skip \
--strict \ --strict \
$PY_FILES $PY_FILES
pyright \ pyright \
@@ -92,10 +85,6 @@ jobs:
--max-line-length=1000 \ --max-line-length=1000 \
--statistics \ --statistics \
$PY_FILES $PY_FILES
isort \
--check \
--diff \
$PY_FILES
- name: Build code (search engine) - name: Build code (search engine)
run: | run: |

View File

@@ -21,7 +21,7 @@ jobs:
matrix: matrix:
libt_version: ["2.0.11", "1.2.20"] libt_version: ["2.0.11", "1.2.20"]
qbt_gui: ["GUI=ON", "GUI=OFF"] qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.6.3"] qt_version: ["6.5.2"]
env: env:
boost_path: "${{ github.workspace }}/../boost" boost_path: "${{ github.workspace }}/../boost"
@@ -30,7 +30,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }} store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false update_packager_index: false
ccache_options: | ccache_options: |
max_size=1G max_size=2G
- name: Install boost - name: Install boost
env: env:
@@ -65,9 +65,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?" tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}" mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -93,7 +90,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \ -DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF -Ddeprecated-functions=OFF
cmake --build build cmake --build build
sudo cmake --install build sudo cmake --install build
@@ -115,7 +112,7 @@ jobs:
-G "Ninja" \ -G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-DCMAKE_INSTALL_PREFIX="/usr" \ -DCMAKE_INSTALL_PREFIX="/usr" \
-DTESTING=ON \ -DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \ -DVERBOSE_CONFIGURE=ON \
@@ -162,7 +159,6 @@ jobs:
- name: Package AppImage - name: Package AppImage
run: | run: |
rm -f "${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/sqldrivers/libqsqlmimer.so"
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt ./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/* rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh

View File

@@ -21,7 +21,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -34,30 +34,21 @@ jobs:
run: | run: |
npm install npm install
npm ls npm ls
echo "::group::npm ls --all"
npm ls --all npm ls --all
echo "::endgroup::"
- name: Run tests
run: npm test
- name: Lint code - name: Lint code
if: ${{ !cancelled() }}
run: npm run lint run: npm run lint
- name: Format code - name: Format code
if: ${{ !cancelled() }}
run: | run: |
npm run format npm run format
git diff --exit-code git diff --exit-code
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3
if: ${{ !cancelled() }}
with: with:
config-file: .github/workflows/helper/codeql/js.yaml config-file: .github/workflows/helper/codeql/js.yaml
languages: javascript languages: javascript
- name: Run CodeQL analysis - name: Run CodeQL analysis
if: ${{ !cancelled() }}
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v3

View File

@@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -67,7 +67,6 @@ jobs:
"set(VCPKG_BUILD_TYPE release)") "set(VCPKG_BUILD_TYPE release)")
# clear buildtrees after each package installation to reduce disk space requirements # clear buildtrees after each package installation to reduce disk space requirements
$packages = ` $packages = `
"boost-build:x64-windows-static-md-release",
"openssl:x64-windows-static-md-release", "openssl:x64-windows-static-md-release",
"zlib:x64-windows-static-md-release" "zlib:x64-windows-static-md-release"
${{ env.vcpkg_path }}/vcpkg.exe upgrade ` ${{ env.vcpkg_path }}/vcpkg.exe upgrade `
@@ -95,18 +94,11 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.." tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
} }
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}" 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 - name: Install Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: "6.9.1" version: "6.8.0"
arch: win64_msvc2022_64 arch: win64_msvc2022_64
archives: qtbase qtsvg qttools archives: qtbase qtsvg qttools
cache: true cache: true
@@ -121,7 +113,7 @@ jobs:
${{ env.libtorrent_path }} ${{ env.libtorrent_path }}
cd ${{ env.libtorrent_path }} cd ${{ env.libtorrent_path }}
$env:CXXFLAGS+=" /guard:cf" $env:CXXFLAGS+=" /guard:cf"
$env:LDFLAGS+=" /GUARD:CF" $env:LDFLAGS+=" /guard:cf"
cmake ` cmake `
-B build ` -B build `
-G "Ninja" ` -G "Ninja" `
@@ -130,7 +122,7 @@ jobs:
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON ` -DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" ` -DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" ` -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 ` -DBUILD_SHARED_LIBS=OFF `
-Ddeprecated-functions=OFF ` -Ddeprecated-functions=OFF `
-Dstatic_runtime=OFF ` -Dstatic_runtime=OFF `
@@ -147,7 +139,7 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo ` -DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON ` -DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" ` -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" ` -DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/install/lib/cmake/LibtorrentRasterbar" `
-DMSVC_RUNTIME_DYNAMIC=ON ` -DMSVC_RUNTIME_DYNAMIC=ON `
-DTESTING=ON ` -DTESTING=ON `
@@ -199,11 +191,6 @@ jobs:
name: qBittorrent-CI_Windows-x64_libtorrent-${{ matrix.libt_version }} name: qBittorrent-CI_Windows-x64_libtorrent-${{ matrix.libt_version }}
path: upload path: upload
- name: Install NSIS
uses: repolevedavaj/install-nsis@265e893c16602d8ccfb0a9ca44173b084078917c # v1.0.3
with:
nsis-version: '3.11'
- name: Create installer - name: Create installer
run: | run: |
7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip" 7z x -o"dist/windows/" "dist/windows/NSISPlugins.zip"

View File

@@ -16,7 +16,7 @@ jobs:
matrix: matrix:
libt_version: ["2.0.11"] libt_version: ["2.0.11"]
qbt_gui: ["GUI=ON"] qbt_gui: ["GUI=ON"]
qt_version: ["6.9.1"] qt_version: ["6.5.2"]
env: env:
boost_path: "${{ github.workspace }}/../boost" boost_path: "${{ github.workspace }}/../boost"
@@ -25,7 +25,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
@@ -39,7 +39,7 @@ jobs:
- name: Install boost - name: Install boost
env: env:
BOOST_MAJOR_VERSION: "1" BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "88" BOOST_MINOR_VERSION: "86"
BOOST_PATCH_VERSION: "0" BOOST_PATCH_VERSION: "0"
run: | 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" 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="$?" tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}" mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
@@ -77,7 +74,7 @@ jobs:
-G "Ninja" \ -G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \ -DCMAKE_CXX_STANDARD=20 \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF -Ddeprecated-functions=OFF
cmake --build build cmake --build build
sudo cmake --install build sudo cmake --install build
@@ -101,7 +98,7 @@ jobs:
-B build \ -B build \
-G "Ninja" \ -G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \ -DBOOST_ROOT="${{ env.boost_path }}" \
-DVERBOSE_CONFIGURE=ON \ -DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }} -D${{ matrix.qbt_gui }}
PATH="${{ env.coverity_path }}/bin:$PATH" \ 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 # but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version. # exception statement from your version.
import argparse
import re
import sys
import xml.etree.ElementTree as ElementTree
from collections.abc import Callable, Sequence from collections.abc import Callable, Sequence
from typing import Optional 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: 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 # but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version. # exception statement from your version.
from collections.abc import Sequence
from typing import Optional
import argparse import argparse
import re import re
import sys import sys
from collections.abc import Sequence
from typing import Optional
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Optional[Sequence[str]] = None) -> int:

View File

@@ -19,7 +19,7 @@ repos:
- ts - ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git - repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v6.0.0 rev: v5.0.0
hooks: hooks:
- id: check-json - id: check-json
name: Check JSON files name: Check JSON files
@@ -69,11 +69,11 @@ repos:
- ts - ts
- repo: https://github.com/codespell-project/codespell.git - repo: https://github.com/codespell-project/codespell.git
rev: v2.4.1 rev: v2.4.0
hooks: hooks:
- id: codespell - id: codespell
name: Check spelling (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: | exclude: |
(?x)^( (?x)^(
.*\.desktop | .*\.desktop |
@@ -88,7 +88,7 @@ repos:
- ts - ts
- repo: https://github.com/crate-ci/typos.git - repo: https://github.com/crate-ci/typos.git
rev: v1.35.3 rev: v1.29.4
hooks: hooks:
- id: typos - id: typos
name: Check spelling (typos) name: Check spelling (typos)

View File

@@ -1,7 +1,7 @@
[main] [main]
host = https://www.transifex.com 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 file_filter = src/lang/qbittorrent_<lang>.ts
source_file = src/lang/qbittorrent_en.ts source_file = src/lang/qbittorrent_en.ts
source_lang = en source_lang = en
@@ -9,7 +9,7 @@ type = QT
minimum_perc = 23 minimum_perc = 23
lang_map = pt: pt_PT, zh: zh_CN 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 file_filter = src/webui/www/translations/webui_<lang>.ts
source_file = src/webui/www/translations/webui_en.ts source_file = src/webui/www/translations/webui_en.ts
source_lang = en source_lang = en

View File

@@ -8,7 +8,7 @@ project(qBittorrent
# version requirements - older versions may work, but you are on your own # version requirements - older versions may work, but you are on your own
set(minBoostVersion 1.76) set(minBoostVersion 1.76)
set(minQt6Version 6.6.0) set(minQt6Version 6.5.0)
set(minOpenSSLVersion 3.0.2) set(minOpenSSLVersion 3.0.2)
set(minLibtorrent1Version 1.2.19) set(minLibtorrent1Version 1.2.19)
set(minLibtorrentVersion 2.0.10) set(minLibtorrentVersion 2.0.10)

111
Changelog
View File

@@ -1,4 +1,113 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0 Mon Jun 23rd 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.1
- BUGFIX: Don't interpret wildcard pattern as filepath globbing (glassez)
- BUGFIX: Fix appearance of search history length spinbox (glassez)
- BUGFIX: Remove dubious seeding time max value (glassez)
- BUGFIX: Fix ratio handling (glassez)
- BUGFIX: Fix compilation with Qt 6.6.0 (glassez)
- WEBUI: Make General tab text selectable by default (dezza)
- WEBUI: Add versioning to local preferences (Chocobo1)
- WEBUI: Make multi-rename search & replace fields use a monospace font (Atk)
- WEBUI: Fix wrong replacement sequence in IPv6 string (Chocobo1)
- WEBUI: Fix memory leak (bolshoytoster)
- WEBUI: Fix path autofill in set location and new category (tehcneko)
- RSS: Mark matched article as "read" if it refers to a duplicate torrent (glassez)
- WINDOWS: Update command line help message (KanishkaHalder1771)
- WINDOWS: NSIS: Don't require agreement on the license page (Chocobo1)
- LINUX: Fix preview not opening on Wayland (Isak05)
- LINUX: Add fallback for random number generator (Chocobo1)
Sun Apr 27th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
- FEATURE: Enable customizing the save statistics time interval (Burnerelu)
- FEATURE: Add drag support to torrent content widget (Chocobo1)
- FEATURE: Display External IP Address in status bar (Thomas Piccirello)
- FEATURE: Use modern functions to get random numbers under Linux/Windows (security related) (Chocobo1)
- FEATURE: Add eXact Length parameter when creating magnet URI (antanilol)
- FEATURE: Support fetching tracker list from URL (Thomas Piccirello)
- FEATURE: Add `announce_port` support (Maxime Thiebaut)
- BUGFIX: Enable adaptive step size for upload and download limits (Harald Nordgren)
- BUGFIX: Add URL link for reverse proxy setup examples (Chocobo1)
- BUGFIX: Allow drop action only on transfer list (Chocobo1)
- BUGFIX: Fix the tab order in dialogs (thalieht)
- BUGFIX: Fix filesize sorting in preview dialog (DoubleSpicy)
- BUGFIX: Improve the speed icons in the status bar (Mahdi Hosseinzadeh)
- BUGFIX: Update link to news (tinyboxvk)
- BUGFIX: Fix tab stop order in various dialogs and UI elements (Chocobo1)
- BUGFIX: Make links accessible by keyboard (Chocobo1)
- BUGFIX: Make tab key switch focus (Chocobo1)
- BUGFIX: Revise DHT bootstrap node list (stalkerok, Chocobo1)
- BUGFIX: Return first tracker as fallback for "current tracker" (glassez)
- BUGFIX: Prevent crash when exiting app with `Add torrent` dialogs opened (glassez)
- BUGFIX: Fix torrent relocating files when switching to "manual" mode (glassez)
- BUGFIX: Prevent crash due to corrupted resume data (glassez)
- WEBUI: Improvements that should help with assistive technologies (Chocobo1)
- WEBUI: Internal refactoring to migrate away from MooTools and towards native browser APIs (Chocobo1, skomerko)
- WEBUI: Implement path autocompletion (Paweł Kotiuk)
- WEBUI: Implement double-click behavior controls (Hanabishi)
- WEBUI: Add ability to toggle alternating row colors in tables (skomerko)
- WEBUI: Improve visibility of unread RSS articles (skomerko)
- WEBUI: Remove deleted torrents even if they are currently filtered out (Carmelo Scandaliato)
- WEBUI: Highlight torrent category in context menu (skomerko)
- WEBUI: Implement 'Auto hide zero status filters' (skomerko)
- WEBUI: Allow to filter torrent list by save path (skomerko)
- WEBUI: Handle regex syntax error for torrent filtering (HamletDuFromage)
- WEBUI: Add missing icons (skomerko)
- WEBUI: Add link to 'List of alternative WebUI' wiki page in Options (Chocobo1)
- WEBUI: Improve properties panel, torrent deletion dialog, filter list, subcategories, torrent deletion, statistics window (skomerko)
- WEBUI: Allow to display only hostname in the Tracker column (skomerko)
- WEBUI: Show country/region name next to its flag (skomerko)
- WEBUI: Improve hash copy actions in context menu (skomerko)
- WEBUI: Support removing tracker from all torrents in WebUI/WebAPI (Thomas Piccirello)
- WEBUI: Display DHT information in the Status bar only when DHT is enabled (skomerko)
- WEBUI: Add 'Confirm torrent recheck' option (skomerko)
- WEBUI: Support managing web seeds (Thomas Piccirello)
- WEBUI: Add colors to log table rows (skomerko)
- WEBUI: Prevent text selection within tabs, menu items (skomerko)
- WEBUI: Use correct text and background colors in RSS details view (skomerko)
- WEBUI: Reduce padding in torrents table (skomerko)
- WEBUI: Add WebAPI/WebUI for managing cookies (Thomas Piccirello)
- WEBUI: Support updating RSS feed URL (Thomas Piccirello)
- WEBUI: Add 'Engine' column to Search table (skomerko)
- WEBUI: Add confirm dialog for Auto TMM (skomerko)
- WEBUI: Add context menu to search tabs (skomerko)
- WEBUI: Show file filter when Content tab selected on load (Thomas Piccirello)
- WEBUI: DHT, PeX and LSD rows are now always on top in Trackers table (skomerko)
- WEBUI: Clear properties panel when torrent no longer selected (skomerko)
- WEBUI: Support auto resizing table columns (Thomas Piccirello)
- WEBUI: Fix displaying RSS panel on load (Thomas Piccirello)
- WEBUI: Add tooltip to regex filter button (Patrik Elfström)
- WEBUI: Hide context menu when clicking on a table row (Patrik Elfström)
- WEBUI: Display torrent progress percentage in General tab (skomerko)
- WEBUI: Use thin scrollbars (skomerko)
- WEBUI: Show 'Rename...' context menu item only when one torrent is selected (skomerko)
- WEBUI: Display error when download fails (Thomas Piccirello)
- WEBUI: Add colors to 'Status' column in Trackers table (skomerko)
- WEBUI: Add missing icon to 'Queue' context menu item (skomerko)
- WEBUI: Change filter inputs to type search (Patrik Elfström)
- WEBUI: Allow to move state icon to name column in torrents table (skomerko)
- WEBUI: Fix bug where the 'Tracker editing' dialog displays incorrect data (skomerko)
- WEBUI: Maintain row highlight after rearranging table columns (skomerko)
- WEBUI: Fix preferences not applied in magnet handler (Chocobo1)
- WEBUI: Update sort icon after changing column order (skomerko)
- WEBUI: Show 'Edit tracker URL...' only when one tracker is selected (skomerko)
- WEBUI: Set status filter to 'All' if selected filter is no longer visible (skomerko)
- WEBAPI: Don't reannounce when removing tracker via WebAPI (Thomas Piccirello)
- WEBAPI: Add WebAPI for managing torrent webseeds (Thomas Piccirello)
- WEBAPI: Add `forced` parameter to `torrents/add` (Chris B)
- WEBAPI: Optionally include trackers list in torrent info response (ze0s)
- WEBAPI: Add new method `setTags` to upsert tags on torrents (ze0s)
- RSS: Resolve relative URLs within RSS article description (Zentino)
- SEARCH: Provide SSL context field (Chocobo1)
- SEARCH: Allow to refresh existing search (glassez)
- SEARCH: Allow multiple simultaneous searches (glassez)
- SEARCH: Store opened search tabs (glassez)
- SEARCH: Store search history (glassez)
- SEARCH: Migrate socks.py from SocksiPy to PySocks 1.7.1 (FredBill1)
- SEARCH: Bump Python version minimum requirement (Chocobo1)
- WINDOWS: Opt into Windows SegmentHeap (Andarwinux)
- WINDOWS: Allow to choose color scheme on Windows (glassez)
- WINDOWS: Verify hash of Python installer (Chocobo1)
- LINUX: Add support for Thunar file manager (algebnaly)
- MACOS: Fix shift-click selection on macOS (Luke Memet)
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1 Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi) - FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)

View File

@@ -11,7 +11,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
- OpenSSL >= 3.0.2 - OpenSSL >= 3.0.2
- Qt 6.6.0 - 6.x - Qt 6.5.0 - 6.x
- zlib >= 1.2.11 - zlib >= 1.2.11

View File

@@ -1,88 +0,0 @@
# WebAPI Changelog
## 2.14.0
* [#23202](https://github.com/qbittorrent/qBittorrent/pull/23202)
* WebAPI responds with the error message "Endpoint does not exist" when the endpoint does not exist, to better differentiate from unrelated Not Found (i.e. 404) responses
* `auth/login` endpoint responds to invalid credentials with a 401
* `torrents/add` endpoint responds with `success_count`, `pending_count`, `failure_count`, and `added_torrent_ids`
* When `pending_count` is non-zero, response code 202 is used
* When all torrents fail to be added, response code 409 is used
## 2.13.1
* [#23163](https://github.com/qbittorrent/qBittorrent/pull/23163)
* `torrents/add` endpoint now supports downloading from a search plugin via the `downloader` parameter
* `torrents/fetchMetadata` endpoint now supports fetching from a search plugin via the `downloader` parameter
* [#23088](https://github.com/qbittorrent/qBittorrent/pull/23088)
* Add `clientdata/load` and `clientdata/store` endpoints for managing WebUI-specific client settings and other shared data
## 2.13.0
* [#23045](https://github.com/qbittorrent/qBittorrent/pull/23045)
* `torrents/trackers` returns three new fields: `next_announce`, `min_announce` and `endpoints`
* `endpoints` is an array of tracker endpoints, each with `name`, `updating`, `status`, `msg`, `bt_version`, `num_peers`, `num_peers`, `num_leeches`, `num_downloaded`, `next_announce` and `min_announce` fields
* `torrents/trackers` now returns `5` and `6` in `status` field as possible values
* `5` for `Tracker error` and `6` for `Unreachable`
* [#22963](https://github.com/qbittorrent/qBittorrent/pull/22963)
* `torrents/editTracker` endpoint now supports setting a tracker's tier via `tier` parameter
* `torrents/editTracker` endpoint always responds with a 204 when successful
* `torrents/editTracker` endpoint `origUrl` parameter renamed to `url`
* [#23061](https://github.com/qbittorrent/qBittorrent/pull/23061)
* `sync/torrentPeers` returns one new field: `i2p_dest`, only when the peer is from I2P
* In this case, the fields `ip` and `port` are not returned
* [#23085](https://github.com/qbittorrent/qBittorrent/pull/23085)
* `torrents/parseMetadata` now responds with an array of metadata in the same order as the files in the request. It previously responded with an object keyed off of the submitted file name.
## 2.12.1
* [#23031](https://github.com/qbittorrent/qBittorrent/pull/23031)
* Add `torrents/setComment` endpoint with parameters `hashes` and `comment` for setting a new torrent comment
## 2.12.0
* [#22989](https://github.com/qbittorrent/qBittorrent/pull/22989)
* `sync/maindata` returns one new field: `share_limit_action`
* `torrents/setShareLimits` now requires a new `shareLimitAction` param that sets a torrent's shareLimitAction property
* possible values `Default`, `Stop`, `Remove`, `RemoveWithContent` and `EnableSuperSeeding`
## 2.11.10
* [#22958](https://github.com/qbittorrent/qBittorrent/pull/22958)
* `torrents/categories` and `sync/maindata` now serialize categories' `downloadPath` to `null`, rather than `undefined`
* [#22954](https://github.com/qbittorrent/qBittorrent/pull/22954)
* `torrents/reannounce` supports specifying individual trackers via `trackers` field
## 2.11.9
* [#21015](https://github.com/qbittorrent/qBittorrent/pull/21015)
* Add `torrents/fetchMetadata` endpoint for retrieving torrent metadata associated with a URL
* Add `torrents/parseMetadata` endpoint for retrieving torrent metadata associated with a .torrent file
* Add `torrents/saveMetadata` endpoint for saving retrieved torrent metadata to a .torrent file
* `torrents/add` allows adding a torrent with metadata previously retrieved via `torrents/fetchMetadata` or `torrents/parseMetadata`
* `torrents/add` allows specifying a torrent's file priorities
* [#22698](https://github.com/qbittorrent/qBittorrent/pull/22698)
* `torrents/addTrackers` and `torrents/removeTrackers` now accept `hash=all` and adds/removes the tracker to/from *all* torrents
* For compatibility, `torrents/removeTrackers` still accepts `hash=*` internally we transform it into `all`
* Allow passing a pipe (`|`) separated list of hashes in `hash` for `torrents/addTrackers` and `torrents/removeTrackers`
## 2.11.8
* [#21349](https://github.com/qbittorrent/qBittorrent/pull/21349)
* Handle sending `204 No Content` status code when response contains no data
* Some endpoints still return `200 OK` to ensure smooth transition
* [#22750](https://github.com/qbittorrent/qBittorrent/pull/22750)
* `torrents/info` allows an optional parameter `includeFiles` that defaults to `false`
* Each torrent will contain a new key `files` which will list all files similar to the `torrents/files` endpoint
* [#22813](https://github.com/qbittorrent/qBittorrent/pull/22813)
* `app/getDirectoryContent` allows an optional parameter `withMetadata` to send file metadata
* Fields are `name`, `type`, `size`, `creation_date`, `last_access_date`, `last_modification_date`
* See PR for TypeScript types
## 2.11.7
* [#22166](https://github.com/qbittorrent/qBittorrent/pull/22166)
* `sync/maindata` returns 3 new torrent fields: `has_tracker_warning`, `has_tracker_error`, `has_other_announce_error`
## 2.11.6
* [#22460](https://github.com/qbittorrent/qBittorrent/pull/22460)
* `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present
* `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present
* `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present

View File

@@ -47,9 +47,6 @@ find_package(Boost ${minBoostVersion} REQUIRED)
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED) find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
find_package(ZLIB ${minZlibVersion} REQUIRED) find_package(ZLIB ${minZlibVersion} REQUIRED)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS Core Network Sql Xml LinguistTools) 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) if (DBUS)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS DBus) find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS DBus)
set_package_properties(Qt6DBus PROPERTIES 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 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_FROM_ASCII
QT_NO_CAST_TO_ASCII QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_BYTEARRAY QT_NO_CAST_FROM_BYTEARRAY
QT_NO_CONTEXTLESS_CONNECT
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
QT_USE_QSTRINGBUILDER QT_USE_QSTRINGBUILDER
QT_STRICT_ITERATORS QT_STRICT_ITERATORS
@@ -90,7 +89,7 @@ if (MSVC)
/Zc:__cplusplus /Zc:__cplusplus
) )
target_link_options(qbt_common_cfg INTERFACE target_link_options(qbt_common_cfg INTERFACE
/GUARD:CF /guard:cf
$<$<NOT:$<CONFIG:Debug>>:/OPT:REF /OPT:ICF> $<$<NOT:$<CONFIG:Debug>>:/OPT:REF /OPT:ICF>
# suppress linking warning due to /INCREMENTAL and /OPT:ICF being both ON # suppress linking warning due to /INCREMENTAL and /OPT:ICF being both ON
$<$<CONFIG:RelWithDebInfo>:/INCREMENTAL:NO> $<$<CONFIG:RelWithDebInfo>:/INCREMENTAL:NO>

2
dist/mac/Info.plist vendored
View File

@@ -55,7 +55,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.2.0</string> <string>5.1.1</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

Binary file not shown.

Binary file not shown.

View File

@@ -34,12 +34,12 @@ endforeach()
if (GUI) if (GUI)
install(FILES org.qbittorrent.qBittorrent.desktop install(FILES org.qbittorrent.qBittorrent.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
COMPONENT data COMPONENT data
) )
install(FILES org.qbittorrent.qBittorrent.metainfo.xml install(FILES org.qbittorrent.qBittorrent.metainfo.xml
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/
COMPONENT data COMPONENT data
) )
@@ -55,9 +55,4 @@ if (GUI)
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/status
COMPONENT data COMPONENT data
) )
else()
install(FILES org.qbittorrent.qBittorrent-nox.metainfo.xml
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo
COMPONENT data
)
endif() endif()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

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

@@ -62,6 +62,6 @@
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url> <url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <releases>
<release version="5.2.0~alpha1" date="2025-02-11"/> <release version="5.1.1" date="2025-06-23"/>
</releases> </releases>
</component> </component>

View File

@@ -14,7 +14,7 @@
; 4.5.1.3 -> good ; 4.5.1.3 -> good
; 4.5.1.3.2 -> bad ; 4.5.1.3.2 -> bad
; 4.5.0beta -> bad ; 4.5.0beta -> bad
!define /ifndef QBT_VERSION "5.2.0" !define /ifndef QBT_VERSION "5.1.1"
; Option that controls the installer's window name ; Option that controls the installer's window name
; If set, its value will be used like this: ; If set, its value will be used like this:

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" .TH "QBITTORRENT\-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt"
.SH NAME .SH NAME
@@ -26,7 +26,7 @@ compatible).
qBittorrent\-nox is meant to be controlled via its feature\-rich Web UI qBittorrent\-nox is meant to be controlled via its feature\-rich Web UI
which is accessible as a default on http://localhost:8080. which is accessible as a default on http://localhost:8080.
The Web UI access is secured and the default account user name is 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 .SH OPTIONS
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options. \f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
.PP .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" .TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt"
.SH NAME .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" "Клиент сети БитТоррент для командной строки" .TH "QBITTORRENT\-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки"
.SH НАЗВАНИЕ .SH НАЗВАНИЕ
qBittorrent\-nox \(em клиент сети БитТоррент для командной строки. qBittorrent\-nox \[em] клиент сети БитТоррент для командной строки.
.SH АВТОРЫ .SH АВТОРЫ
Christophe Dumez \c Christophe Dumez \c
.MT chris@qbittorrent.org .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" "Клиент сети БитТоррент" .TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент"
.SH НАЗВАНИЕ .SH НАЗВАНИЕ
qBittorrent \(em клиент сети БитТоррент. qBittorrent \[em] клиент сети БитТоррент.
.SH АВТОРЫ .SH АВТОРЫ
Christophe Dumez \c Christophe Dumez \c
.MT chris@qbittorrent.org .MT chris@qbittorrent.org

View File

@@ -86,19 +86,6 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
PROPERTIES PROPERTIES
MACOSX_PACKAGE_LOCATION Resources 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 # provide variables for substitution in dist/mac/Info.plist
get_target_property(EXECUTABLE_NAME qbt_app OUTPUT_NAME) 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 # This variable name should be changed once qmake is no longer used. Refer to the discussion in PR #14813

View File

@@ -410,7 +410,7 @@ void Application::setMemoryWorkingSetLimit(const int size)
return; return;
m_storeMemoryWorkingSetLimit = size; 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(); applyMemoryWorkingSetLimit();
#endif #endif
} }
@@ -575,9 +575,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
case u'L': case u'L':
str.replace(i, 2, torrent->category()); str.replace(i, 2, torrent->category());
break; break;
case u'M':
str.replace(i, 2, torrent->comment());
break;
case u'N': case u'N':
str.replace(i, 2, torrent->name()); str.replace(i, 2, torrent->name());
break; break;
@@ -662,13 +659,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
{ {
// strip redundant quotes // strip redundant quotes
if (arg.startsWith(u'"') && arg.endsWith(u'"')) if (arg.startsWith(u'"') && arg.endsWith(u'"'))
{ arg = arg.mid(1, (arg.size() - 2));
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
arg.slice(1, (arg.size() - 2));
#else
arg.removeLast().removeFirst();
#endif
}
arg = replaceVariables(arg); arg = replaceVariables(arg);
} }
@@ -677,7 +668,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
QProcess proc; QProcess proc;
proc.setProgram(command); proc.setProgram(command);
proc.setArguments(args); proc.setArguments(args);
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
if (proc.startDetached()) if (proc.startDetached())
{ {
@@ -847,7 +837,7 @@ int Application::exec()
printf("%s\n", qUtf8Printable(loadingStr)); printf("%s\n", qUtf8Printable(loadingStr));
#endif #endif
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS) #if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
applyMemoryWorkingSetLimit(); applyMemoryWorkingSetLimit();
#endif #endif
@@ -1205,7 +1195,7 @@ void Application::shutdownCleanup([[maybe_unused]] QSessionManager &manager)
} }
#endif #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 void Application::applyMemoryWorkingSetLimit() const
{ {
const size_t MiB = 1024 * 1024; const size_t MiB = 1024 * 1024;

View File

@@ -137,7 +137,7 @@ namespace
}; };
// Option with string value. May not have a shortcut // Option with string value. May not have a shortcut
class StringOption : protected Option struct StringOption : protected Option
{ {
public: public:
explicit constexpr StringOption(const QStringView name) explicit constexpr StringOption(const QStringView name)
@@ -147,14 +147,12 @@ namespace
QString value(const QString &arg) const QString value(const QString &arg) const
{ {
const qsizetype index = arg.indexOf(u'='); QStringList parts = arg.split(u'=');
if (index == -1) if (parts.size() == 2)
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'", return Utils::String::unquote(parts[1], u"'\""_s);
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'") "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
.arg(fullParameter(), u"<value>"_s)); .arg(fullParameter(), u"<value>"_s));
const QStringView val = QStringView(arg).sliced(index + 1);
return Utils::String::unquote(val, u"'\""_s).toString();
} }
QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
@@ -170,7 +168,7 @@ namespace
friend bool operator==(const StringOption &option, const QString &arg) friend bool operator==(const StringOption &option, const QString &arg)
{ {
return (arg == option.fullParameter()) || arg.startsWith(option.parameterAssignment()); return arg.startsWith(option.parameterAssignment());
} }
private: private:
@@ -467,13 +465,13 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
return result; 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()}; QStringList lines = {words.first()};
int currentLineMaxLength = wrapAtColumn - initialIndentation; 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) if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
{ {

View File

@@ -175,15 +175,12 @@ void FileLogger::flushLog()
void FileLogger::openLogFile() 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\".") m_logFile.close();
.arg(m_logFile.fileName(), m_logFile.errorString()), Log::CRITICAL); LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
return;
} }
// best effort, don't report error
m_logFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
} }
void FileLogger::closeLogFile() void FileLogger::closeLogFile()

View File

@@ -182,6 +182,9 @@ int main(int argc, char *argv[])
adjustFileDescriptorLimit(); adjustFileDescriptorLimit();
#endif #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 // `app` must be declared out of try block to allow display message box in case of exception
std::unique_ptr<Application> app; std::unique_ptr<Application> app;
try try
@@ -201,27 +204,34 @@ int main(int argc, char *argv[])
#endif #endif
const QBtCommandLineParameters params = app->commandLineArgs(); 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()) if (!params.unknownParameter.isEmpty())
{ {
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 is an unknown command line parameter.", throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 is an unknown command line parameter.",
"--random-parameter is an unknown command line parameter.") "--random-parameter is an unknown command line parameter.")
.arg(params.unknownParameter)); .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 // Check if qBittorrent is already running
if (app->hasAnotherInstance()) if (app->hasAnotherInstance())

View File

@@ -55,7 +55,6 @@ add_library(qbt_base STATIC
concepts/stringable.h concepts/stringable.h
digest32.h digest32.h
exceptions.h exceptions.h
freediskspacechecker.h
global.h global.h
http/connection.h http/connection.h
http/httperror.h http/httperror.h
@@ -161,7 +160,6 @@ add_library(qbt_base STATIC
bittorrent/trackerentry.cpp bittorrent/trackerentry.cpp
bittorrent/trackerentrystatus.cpp bittorrent/trackerentrystatus.cpp
exceptions.cpp exceptions.cpp
freediskspacechecker.cpp
http/connection.cpp http/connection.cpp
http/httperror.cpp http/httperror.cpp
http/requestparser.cpp http/requestparser.cpp

View File

@@ -29,7 +29,6 @@
#include "addtorrentmanager.h" #include "addtorrentmanager.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h" #include "base/bittorrent/torrentdescriptor.h"
@@ -186,8 +185,8 @@ void AddTorrentManager::handleDuplicateTorrent(const QString &source
message = tr("Trackers are merged from new 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") LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
.arg(source, existingTorrent->name(), existingTorrent->infoHash().toString(), message)); .arg(source, existingTorrent->name(), message));
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message}); emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
} }

View File

@@ -35,6 +35,7 @@
#include <QObject> #include <QObject>
#include "base/applicationcomponent.h" #include "base/applicationcomponent.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/addtorrentparams.h" #include "base/bittorrent/addtorrentparams.h"
#include "base/torrentfileguard.h" #include "base/torrentfileguard.h"
@@ -44,7 +45,6 @@ namespace BitTorrent
class Session; class Session;
class Torrent; class Torrent;
class TorrentDescriptor; class TorrentDescriptor;
struct AddTorrentError;
} }
namespace Net namespace Net

View File

@@ -189,13 +189,8 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
return; 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}; const QRegularExpression hashPattern {u"^([A-Fa-f0-9]{40})$"_s};
qsizetype queuePos = 0; int start = 0;
while (true) while (true)
{ {
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed()); const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
@@ -206,15 +201,11 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
if (rxMatch.hasMatch()) if (rxMatch.hasMatch())
{ {
const auto torrentID = BitTorrent::TorrentID::fromString(rxMatch.captured(1)); 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 != -1)
{ {
if (pos != queuePos) std::swap(m_registeredTorrents[start], m_registeredTorrents[pos]);
{ ++start;
m_registeredTorrents.swapItemsAt(pos, queuePos);
registeredTorrentsIndexes.insert(m_registeredTorrents.at(pos), pos);
}
++queuePos;
} }
} }
} }
@@ -236,7 +227,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
LoadTorrentParams torrentParams; LoadTorrentParams torrentParams;
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category")); torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name")); 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.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority"); torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME); torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
@@ -352,9 +342,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
return torrentParams; 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); m_asyncWorker->store(id, resumeData);
}); });
@@ -438,7 +428,6 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
data["qBt-category"] = resumeData.category.toStdString(); data["qBt-category"] = resumeData.category.toStdString();
data["qBt-tags"] = setToEntryList(resumeData.tags); data["qBt-tags"] = setToEntryList(resumeData.tags);
data["qBt-name"] = resumeData.name.toStdString(); data["qBt-name"] = resumeData.name.toStdString();
data["qBt-comment"] = resumeData.comment.toStdString();
data["qBt-seedStatus"] = resumeData.hasFinishedStatus; data["qBt-seedStatus"] = resumeData.hasFinishedStatus;
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString(); data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority; data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;

View File

@@ -50,7 +50,7 @@ namespace BitTorrent
QList<TorrentID> registeredTorrents() const override; QList<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) 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 remove(const TorrentID &id) const override;
void storeQueue(const QList<TorrentID> &queue) 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 QJsonObject BitTorrent::CategoryOptions::toJSON() const
{ {
QJsonValue downloadPathValue = QJsonValue::Null; QJsonValue downloadPathValue = QJsonValue::Undefined;
if (downloadPath) if (downloadPath)
{ {
if (downloadPath->enabled) if (downloadPath->enabled)

View File

@@ -67,7 +67,7 @@ namespace
{ {
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s; 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_META = u"meta"_s;
const QString DB_TABLE_TORRENTS = u"torrents"_s; const QString DB_TABLE_TORRENTS = u"torrents"_s;
@@ -86,7 +86,7 @@ namespace
class StoreJob final : public Job class StoreJob final : public Job
{ {
public: public:
StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData); StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
void perform(QSqlDatabase db) override; void perform(QSqlDatabase db) override;
private: private:
@@ -131,7 +131,6 @@ namespace
const Column DB_COLUMN_NAME = makeColumn(u"name"_s); const Column DB_COLUMN_NAME = makeColumn(u"name"_s);
const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s); const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s);
const Column DB_COLUMN_TAGS = makeColumn(u"tags"_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_TARGET_SAVE_PATH = makeColumn(u"target_save_path"_s);
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn(u"download_path"_s); const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn(u"download_path"_s);
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn(u"content_layout"_s); const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn(u"content_layout"_s);
@@ -232,7 +231,7 @@ namespace BitTorrent
void run() override; void run() override;
void requestInterruption(); void requestInterruption();
void store(const TorrentID &id, LoadTorrentParams resumeData); void store(const TorrentID &id, const LoadTorrentParams &resumeData);
void remove(const TorrentID &id); void remove(const TorrentID &id);
void storeQueue(const QList<TorrentID> &queue); void storeQueue(const QList<TorrentID> &queue);
@@ -328,9 +327,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor
return parseQueryResultRow(query); 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 void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const
@@ -462,7 +461,6 @@ void BitTorrent::DBResumeDataStorage::createDB() const
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_TAGS, 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_TARGET_SAVE_PATH, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, u"TEXT NOT NULL"_s), makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, u"TEXT NOT NULL"_s),
@@ -580,9 +578,6 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
throw RuntimeError(query.lastError().text()); 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}); const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
if (!query.prepare(updateMetaVersionQuery)) if (!query.prepare(updateMetaVersionQuery))
throw RuntimeError(query.lastError().text()); throw RuntimeError(query.lastError().text());
@@ -624,7 +619,6 @@ LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &q
LoadTorrentParams resumeData; LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString(); resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.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(); const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty()) if (!tagsData.isEmpty())
{ {
@@ -775,9 +769,9 @@ void DBResumeDataStorage::Worker::requestInterruption()
m_waitCondition.wakeAll(); 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) void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
@@ -803,9 +797,9 @@ namespace
{ {
using namespace BitTorrent; using namespace BitTorrent;
StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData) StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
: m_torrentID {torrentID} : m_torrentID {torrentID}
, m_resumeData {std::move(resumeData)} , m_resumeData {resumeData}
{ {
} }
@@ -840,7 +834,6 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData)
DB_COLUMN_NAME, DB_COLUMN_NAME,
DB_COLUMN_CATEGORY, DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS, DB_COLUMN_TAGS,
DB_COLUMN_COMMENT,
DB_COLUMN_TARGET_SAVE_PATH, DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH, DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT, DB_COLUMN_CONTENT_LAYOUT,
@@ -906,7 +899,6 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData)
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category); query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty() query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
? QString() : Utils::String::joinIntoString(m_resumeData.tags, u","_s))); ? 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_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_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit); query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);

View File

@@ -49,7 +49,7 @@ namespace BitTorrent
QList<TorrentID> registeredTorrents() const override; QList<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) 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 remove(const TorrentID &id) const override;
void storeQueue(const QList<TorrentID> &queue) const override; void storeQueue(const QList<TorrentID> &queue) const override;

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Thomas Piccirello <thomas@piccirello.com> * Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Thomas Piccirello <thomas@piccirello.com> * Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -27,14 +27,13 @@
*/ */
#include "filesearcher.h" #include "filesearcher.h"
#include <QPromise>
#include "base/bittorrent/common.h" #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; bool found = false;
for (Path &fileName : fileNames) for (Path &fileName : fileNames)
@@ -59,12 +58,8 @@ namespace
} }
return found; return found;
} };
}
void FileSearcher::search(const PathList &originalFileNames, const Path &savePath
, const Path &downloadPath, const bool forceAppendExt, QPromise<FileSearchResult> &promise)
{
Path usedPath = savePath; Path usedPath = savePath;
PathList adjustedFileNames = originalFileNames; PathList adjustedFileNames = originalFileNames;
const bool found = findInDir(usedPath, adjustedFileNames, (forceAppendExt && downloadPath.isEmpty())); 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); 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. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -32,13 +32,10 @@
#include "base/path.h" #include "base/path.h"
template <typename T> class QPromise; namespace BitTorrent
struct FileSearchResult
{ {
Path savePath; class TorrentID;
PathList fileNames; }
};
class FileSearcher final : public QObject class FileSearcher final : public QObject
{ {
@@ -46,8 +43,12 @@ class FileSearcher final : public QObject
Q_DISABLE_COPY_MOVE(FileSearcher) Q_DISABLE_COPY_MOVE(FileSearcher)
public: public:
using QObject::QObject; FileSearcher() = default;
void search(const PathList &originalFileNames, const Path &savePath public slots:
, const Path &downloadPath, bool forceAppendExt, QPromise<FileSearchResult> &promise); 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 "infohash.h"
#include <QHash> #include <QHash>
#include <QString>
#include "base/global.h"
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>(); const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
@@ -89,28 +86,6 @@ BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
#endif #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 BitTorrent::InfoHash::operator WrappedType() const
{ {
return m_nativeHash; return m_nativeHash;

View File

@@ -36,8 +36,6 @@
#include "base/digest32.h" #include "base/digest32.h"
class QString;
using SHA1Hash = Digest32<160>; using SHA1Hash = Digest32<160>;
using SHA256Hash = Digest32<256>; using SHA256Hash = Digest32<256>;
@@ -81,8 +79,6 @@ namespace BitTorrent
SHA256Hash v2() const; SHA256Hash v2() const;
TorrentID toTorrentID() const; TorrentID toTorrentID() const;
QString toString() const;
operator WrappedType() const; operator WrappedType() const;
private: private:

View File

@@ -50,7 +50,6 @@ namespace BitTorrent
TagSet tags; TagSet tags;
Path savePath; Path savePath;
Path downloadPath; Path downloadPath;
QString comment;
TorrentContentLayout contentLayout = TorrentContentLayout::Original; TorrentContentLayout contentLayout = TorrentContentLayout::Original;
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged; TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false; bool useAutoTMM = false;

View File

@@ -39,11 +39,7 @@ PeerAddress PeerAddress::parse(const QStringView address)
if (address.startsWith(u'[') && address.contains(u"]:")) if (address.startsWith(u'[') && address.contains(u"]:"))
{ // IPv6 { // IPv6
ipPort = address.split(u"]:"); ipPort = address.split(u"]:");
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) ipPort[0] = ipPort[0].mid(1); // chop '['
ipPort[0].slice(1); // chop '['
#else
ipPort[0] = ipPort[0].sliced(1); // chop '['
#endif
} }
else if (address.contains(u':')) else if (address.contains(u':'))
{ // IPv4 { // IPv4

View File

@@ -71,8 +71,8 @@ QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedRe
return loadedResumeData; 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}; 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 QList<TorrentID> registeredTorrents() const = 0;
virtual LoadResumeDataResult load(const TorrentID &id) 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 remove(const TorrentID &id) const = 0;
virtual void storeQueue(const QList<TorrentID> &queue) const = 0; virtual void storeQueue(const QList<TorrentID> &queue) const = 0;
@@ -72,7 +72,7 @@ namespace BitTorrent
void loadFinished(); void loadFinished();
protected: protected:
void onResumeDataLoaded(const TorrentID &torrentID, LoadResumeDataResult loadResumeDataResult) const; void onResumeDataLoaded(const TorrentID &torrentID, const LoadResumeDataResult &loadResumeDataResult) const;
private: private:
virtual void doLoadAll() const = 0; virtual void doLoadAll() const = 0;

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -422,8 +422,6 @@ namespace BitTorrent
virtual void setUTPRateLimited(bool limited) = 0; virtual void setUTPRateLimited(bool limited) = 0;
virtual MixedModeAlgorithm utpMixedMode() const = 0; virtual MixedModeAlgorithm utpMixedMode() const = 0;
virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0; virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0;
virtual int hostnameCacheTTL() const = 0;
virtual void setHostnameCacheTTL(int value) = 0;
virtual bool isIDNSupportEnabled() const = 0; virtual bool isIDNSupportEnabled() const = 0;
virtual void setIDNSupportEnabled(bool enabled) = 0; virtual void setIDNSupportEnabled(bool enabled) = 0;
virtual bool multiConnectionsPerIpEnabled() const = 0; virtual bool multiConnectionsPerIpEnabled() const = 0;
@@ -482,8 +480,6 @@ namespace BitTorrent
virtual QString lastExternalIPv4Address() const = 0; virtual QString lastExternalIPv4Address() const = 0;
virtual QString lastExternalIPv6Address() const = 0; virtual QString lastExternalIPv6Address() const = 0;
virtual qint64 freeDiskSpace() const = 0;
signals: signals:
void startupProgressUpdated(int progress); void startupProgressUpdated(int progress);
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason); void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
@@ -493,6 +489,7 @@ namespace BitTorrent
void categoryOptionsChanged(const QString &categoryName); void categoryOptionsChanged(const QString &categoryName);
void fullDiskError(Torrent *torrent, const QString &msg); void fullDiskError(Torrent *torrent, const QString &msg);
void IPFilterParsed(bool error, int ruleCount); void IPFilterParsed(bool error, int ruleCount);
void loadTorrentFailed(const QString &error);
void metadataDownloaded(const TorrentInfo &info); void metadataDownloaded(const TorrentInfo &info);
void restored(); void restored();
void paused(); void paused();
@@ -523,6 +520,5 @@ namespace BitTorrent
void trackerSuccess(Torrent *torrent, const QString &tracker); void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker); void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers); 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. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -30,7 +30,6 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <functional>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -62,16 +61,11 @@ class QString;
class QTimer; class QTimer;
class QUrl; class QUrl;
template <typename T> class QFuture;
class BandwidthScheduler; class BandwidthScheduler;
class FileSearcher; class FileSearcher;
class FilterParserThread; class FilterParserThread;
class FreeDiskSpaceChecker;
class NativeSessionExtension; class NativeSessionExtension;
struct FileSearchResult;
namespace BitTorrent namespace BitTorrent
{ {
enum class MoveStorageMode; enum class MoveStorageMode;
@@ -396,8 +390,6 @@ namespace BitTorrent
void setUTPRateLimited(bool limited) override; void setUTPRateLimited(bool limited) override;
MixedModeAlgorithm utpMixedMode() const override; MixedModeAlgorithm utpMixedMode() const override;
void setUtpMixedMode(MixedModeAlgorithm mode) override; void setUtpMixedMode(MixedModeAlgorithm mode) override;
int hostnameCacheTTL() const override;
void setHostnameCacheTTL(int value) override;
bool isIDNSupportEnabled() const override; bool isIDNSupportEnabled() const override;
void setIDNSupportEnabled(bool enabled) override; void setIDNSupportEnabled(bool enabled) override;
bool multiConnectionsPerIpEnabled() const override; bool multiConnectionsPerIpEnabled() const override;
@@ -456,8 +448,6 @@ namespace BitTorrent
QString lastExternalIPv4Address() const override; QString lastExternalIPv4Address() const override;
QString lastExternalIPv6Address() const override; QString lastExternalIPv6Address() const override;
qint64 freeDiskSpace() const override;
// Torrent interface // Torrent interface
void handleTorrentResumeDataRequested(const TorrentImpl *torrent); void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *torrent); void handleTorrentShareLimitChanged(TorrentImpl *torrent);
@@ -477,15 +467,14 @@ namespace BitTorrent
void handleTorrentTrackersChanged(TorrentImpl *torrent); void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds); void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds); 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 handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent); void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context); 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); void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const;
QFuture<FileSearchResult> findIncompleteFiles(const Path &savePath, const Path &downloadPath, const PathList &filePaths = {}) const;
void enablePortMapping(); void enablePortMapping();
void disablePortMapping(); void disablePortMapping();
@@ -520,6 +509,7 @@ namespace BitTorrent
void generateResumeData(); void generateResumeData();
void handleIPFilterParsed(int ruleCount); void handleIPFilterParsed(int ruleCount);
void handleIPFilterError(); void handleIPFilterError();
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage); void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
private: private:
@@ -578,7 +568,8 @@ namespace BitTorrent
void updateSeedingLimitTimer(); void updateSeedingLimitTimer();
void exportTorrentFile(const Torrent *torrent, const Path &folderPath); 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 handleAddTorrentAlert(const lt::add_torrent_alert *alert);
void handleStateUpdateAlert(const lt::state_update_alert *alert); void handleStateUpdateAlert(const lt::state_update_alert *alert);
void handleMetadataReceivedAlert(const lt::metadata_received_alert *alert); void handleMetadataReceivedAlert(const lt::metadata_received_alert *alert);
@@ -605,21 +596,9 @@ namespace BitTorrent
void handleTrackerAlert(const lt::tracker_alert *alert); void handleTrackerAlert(const lt::tracker_alert *alert);
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
void handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert); void handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert);
void handleFilePrioAlert(const lt::file_prio_alert *alert);
#endif #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 *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params);
TorrentImpl *getTorrent(const lt::torrent_handle &nativeHandle) const;
QList<TorrentImpl *> getQueuedTorrentsByID(const QList<TorrentID> &torrentIDs) const;
void saveResumeData(); void saveResumeData();
void saveTorrentsQueue(); void saveTorrentsQueue();
@@ -704,7 +683,6 @@ namespace BitTorrent
CachedSettingValue<BTProtocol> m_btProtocol; CachedSettingValue<BTProtocol> m_btProtocol;
CachedSettingValue<bool> m_isUTPRateLimited; CachedSettingValue<bool> m_isUTPRateLimited;
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode; CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
CachedSettingValue<int> m_hostnameCacheTTL;
CachedSettingValue<bool> m_IDNSupportEnabled; CachedSettingValue<bool> m_IDNSupportEnabled;
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled; CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate; CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
@@ -826,13 +804,11 @@ namespace BitTorrent
FileSearcher *m_fileSearcher = nullptr; FileSearcher *m_fileSearcher = nullptr;
TorrentContentRemover *m_torrentContentRemover = 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, lt::torrent_handle> m_downloadedMetadata;
QHash<TorrentID, TorrentImpl *> m_torrents; QHash<TorrentID, TorrentImpl *> m_torrents;
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID; QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
QHash<TorrentID, RemovingTorrentData> m_removingTorrents; QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
QHash<TorrentID, TorrentID> m_changedTorrentIDs; QHash<TorrentID, TorrentID> m_changedTorrentIDs;
QMap<QString, CategoryOptions> m_categories; QMap<QString, CategoryOptions> m_categories;
@@ -874,10 +850,6 @@ namespace BitTorrent
QList<TorrentImpl *> m_pendingFinishedTorrents; QList<TorrentImpl *> m_pendingFinishedTorrents;
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
qint64 m_freeDiskSpace = -1;
friend void Session::initInstance(); friend void Session::initInstance();
friend void Session::freeInstance(); friend void Session::freeInstance();
friend Session *Session::instance(); friend Session *Session::instance();

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -45,8 +45,6 @@ class QByteArray;
class QDateTime; class QDateTime;
class QUrl; class QUrl;
template <typename T> class QFuture;
namespace BitTorrent namespace BitTorrent
{ {
enum class DownloadPriority; enum class DownloadPriority;
@@ -144,7 +142,6 @@ namespace BitTorrent
virtual QDateTime creationDate() const = 0; virtual QDateTime creationDate() const = 0;
virtual QString creator() const = 0; virtual QString creator() const = 0;
virtual QString comment() const = 0; virtual QString comment() const = 0;
virtual void setComment(const QString &comment) = 0;
virtual bool isPrivate() const = 0; virtual bool isPrivate() const = 0;
virtual qlonglong totalSize() const = 0; virtual qlonglong totalSize() const = 0;
virtual qlonglong wantedSize() const = 0; virtual qlonglong wantedSize() const = 0;
@@ -276,7 +273,10 @@ namespace BitTorrent
virtual bool isDHTDisabled() const = 0; virtual bool isDHTDisabled() const = 0;
virtual bool isPEXDisabled() const = 0; virtual bool isPEXDisabled() const = 0;
virtual bool isLSDDisabled() const = 0; virtual bool isLSDDisabled() const = 0;
virtual QList<PeerInfo> peers() const = 0;
virtual QBitArray pieces() 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 distributedCopies() const = 0;
virtual qreal maxRatio() const = 0; virtual qreal maxRatio() const = 0;
virtual int maxSeedingTime() const = 0; virtual int maxSeedingTime() const = 0;
@@ -323,10 +323,10 @@ namespace BitTorrent
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0; virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0; virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
virtual QFuture<QList<PeerInfo>> fetchPeerInfo() const = 0; virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
virtual QFuture<QList<QUrl>> fetchURLSeeds() const = 0; virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
virtual QFuture<QList<int>> fetchPieceAvailability() const = 0; virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
virtual QFuture<QBitArray> fetchDownloadingPieces() const = 0; virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
TorrentID id() const; TorrentID id() const;
bool isRunning() const; bool isRunning() const;

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -34,8 +34,6 @@
#include "abstractfilestorage.h" #include "abstractfilestorage.h"
#include "downloadpriority.h" #include "downloadpriority.h"
template <typename T> class QFuture;
namespace BitTorrent namespace BitTorrent
{ {
class TorrentContentHandler : public QObject, public AbstractFileStorage 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 * 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. * 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 prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 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 // need to sort the file names by natural sort order
QStringList dirs = {m_params.sourcePath.data()}; 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()) while (dirIter.hasNext())
{ {
const QFileInfo dirInfo = dirIter.nextFileInfo(); const QString filePath = dirIter.next();
dirs.append(filePath);
#ifdef Q_OS_WIN
// .lnk to directory
// Windows users do not expect torrent creator to traverse into .lnk files so skip over them
if (dirInfo.isShortcut())
continue;
#endif
const QString dirPath = dirInfo.filePath();
dirs.append(dirPath);
} }
std::sort(dirs.begin(), dirs.end(), naturalLessThan); std::sort(dirs.begin(), dirs.end(), naturalLessThan);
@@ -148,29 +146,19 @@ void TorrentCreator::run()
{ {
QStringList tmpNames; // natural sort files within each dir 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()) while (fileIter.hasNext())
{ {
const QFileInfo fileInfo = fileIter.nextFileInfo(); const QFileInfo fileInfo = fileIter.nextFileInfo();
const Path filePath {fileInfo.filePath()};
qint64 fileSize = fileInfo.size();
#ifdef Q_OS_WIN const Path relFilePath = parentPath.relativePathOf(Path(fileInfo.filePath()));
// .lnk to file
// libtorrent couldn't handle .lnk files on Windows
if (fileInfo.isShortcut())
continue;
// file symbolic link
// QFileInfo::size() failed to return the target file size
// and we need to redirect it manually
if (fileInfo.isSymbolicLink())
fileSize = QFileInfo(fileInfo.symLinkTarget()).size();
#endif
const Path relFilePath = parentPath.relativePathOf(filePath);
tmpNames.append(relFilePath.toString()); tmpNames.append(relFilePath.toString());
fileSizeMap[tmpNames.last()] = fileSize; fileSizeMap[tmpNames.last()] = fileInfo.size();
} }
std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan); std::sort(tmpNames.begin(), tmpNames.end(), naturalLessThan);
@@ -186,8 +174,8 @@ void TorrentCreator::run()
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
lt::create_torrent newTorrent {fs, m_params.pieceSize, toNativeTorrentFormatFlag(m_params.torrentFormat)}; lt::create_torrent newTorrent {fs, m_params.pieceSize, toNativeTorrentFormatFlag(m_params.torrentFormat)};
#else #else
lt::create_torrent newTorrent {fs, m_params.pieceSize, m_params.paddedFileSizeLimit lt::create_torrent newTorrent(fs, m_params.pieceSize, m_params.paddedFileSizeLimit
, (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})}; , (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {}));
#endif #endif
// Add url seeds // Add url seeds

View File

@@ -132,7 +132,7 @@ try
const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams); const lt::entry torrentEntry = lt::write_torrent_file(m_ltAddTorrentParams);
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry); const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
if (!result) if (!result)
return nonstd::make_unexpected(result.error()); return result.get_unexpected();
return {}; return {};
} }
@@ -141,22 +141,6 @@ catch (const lt::system_error &err)
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what())); 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) BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTorrentParams)
: m_ltAddTorrentParams {std::move(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> loadFromFile(const Path &path) noexcept;
static nonstd::expected<TorrentDescriptor, QString> parse(const QString &str) noexcept; static nonstd::expected<TorrentDescriptor, QString> parse(const QString &str) noexcept;
nonstd::expected<void, QString> saveToFile(const Path &path) const; nonstd::expected<void, QString> saveToFile(const Path &path) const;
nonstd::expected<QByteArray, QString> saveToBuffer() const;
const lt::add_torrent_params &ltAddTorrentParams() const; const lt::add_torrent_params &ltAddTorrentParams() const;

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -37,10 +37,11 @@
#endif #endif
#include <libtorrent/address.hpp> #include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/session.hpp> #include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp> #include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.hpp> #include <libtorrent/time.hpp>
#include <libtorrent/write_resume_data.hpp>
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
#include <libtorrent/info_hash.hpp> #include <libtorrent/info_hash.hpp>
@@ -50,9 +51,7 @@
#include <QByteArray> #include <QByteArray>
#include <QCache> #include <QCache>
#include <QDebug> #include <QDebug>
#include <QFuture>
#include <QPointer> #include <QPointer>
#include <QPromise>
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
#include <QUrl> #include <QUrl>
@@ -68,7 +67,6 @@
#include "common.h" #include "common.h"
#include "downloadpriority.h" #include "downloadpriority.h"
#include "extensiondata.h" #include "extensiondata.h"
#include "filesearcher.h"
#include "loadtorrentparams.h" #include "loadtorrentparams.h"
#include "ltqbitarray.h" #include "ltqbitarray.h"
#include "lttypecast.h" #include "lttypecast.h"
@@ -151,41 +149,36 @@ namespace
if (ltAnnounceInfo.updating) if (ltAnnounceInfo.updating)
{ {
trackerEndpointStatus.isUpdating = true; trackerEndpointStatus.state = TrackerEndpointState::Updating;
++numUpdating; ++numUpdating;
} }
else else if (ltAnnounceInfo.fails > 0)
{ {
trackerEndpointStatus.isUpdating = false; if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
if (ltAnnounceInfo.fails > 0)
{ {
if (ltAnnounceInfo.last_error == lt::errors::tracker_failure) trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
{ ++numTrackerError;
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;
}
} }
else if (nativeEntry.verified) else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
{ {
trackerEndpointStatus.state = TrackerEndpointState::Working; trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
++numWorking; ++numUnreachable;
} }
else 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()) if (!ltAnnounceInfo.message.empty())
{ {
@@ -219,28 +212,23 @@ namespace
{ {
if (numUpdating > 0) if (numUpdating > 0)
{ {
trackerEntryStatus.isUpdating = true; trackerEntryStatus.state = TrackerEndpointState::Updating;
} }
else else if (numWorking > 0)
{ {
trackerEntryStatus.isUpdating = false; trackerEntryStatus.state = TrackerEndpointState::Working;
}
if (numWorking > 0) else if (numTrackerError > 0)
{ {
trackerEntryStatus.state = TrackerEndpointState::Working; trackerEntryStatus.state = TrackerEndpointState::TrackerError;
} }
else if (numTrackerError > 0) else if (numUnreachable == numEndpoints)
{ {
trackerEntryStatus.state = TrackerEndpointState::TrackerError; trackerEntryStatus.state = TrackerEndpointState::Unreachable;
} }
else if (numUnreachable == numEndpoints) else if ((numUnreachable + numNotWorking) == numEndpoints)
{ {
trackerEntryStatus.state = TrackerEndpointState::Unreachable; trackerEntryStatus.state = TrackerEndpointState::NotWorking;
}
else if ((numUnreachable + numNotWorking) == numEndpoints)
{
trackerEntryStatus.state = TrackerEndpointState::NotWorking;
}
} }
} }
@@ -299,9 +287,11 @@ namespace
// TorrentImpl // 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) : Torrent(session)
, m_session(session) , m_session(session)
, m_nativeSession(nativeSession)
, m_nativeHandle(nativeHandle) , m_nativeHandle(nativeHandle)
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
, m_infoHash(m_nativeHandle.info_hashes()) , m_infoHash(m_nativeHandle.info_hashes())
@@ -324,7 +314,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeH
, m_useAutoTMM(params.useAutoTMM) , m_useAutoTMM(params.useAutoTMM)
, m_isStopped(params.stopped) , m_isStopped(params.stopped)
, m_sslParams(params.sslParameters) , m_sslParams(params.sslParameters)
, m_ltAddTorrentParams(std::move(params.ltAddTorrentParams)) , m_ltAddTorrentParams(params.ltAddTorrentParams)
, m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit)) , m_downloadLimit(cleanLimitValue(m_ltAddTorrentParams.download_limit))
, m_uploadLimit(cleanLimitValue(m_ltAddTorrentParams.upload_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); setStopCondition(params.stopCondition);
const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata); const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
@@ -443,15 +430,6 @@ QString TorrentImpl::comment() const
return m_comment; return m_comment;
} }
void TorrentImpl::setComment(const QString &comment)
{
if (m_comment != comment)
{
m_comment = comment;
deferredRequestResumeData();
}
}
bool TorrentImpl::isPrivate() const bool TorrentImpl::isPrivate() const
{ {
return m_torrentInfo.isPrivate(); return m_torrentInfo.isPrivate();
@@ -1459,7 +1437,7 @@ int TorrentImpl::totalLeechersCount() const
int TorrentImpl::downloadLimit() const int TorrentImpl::downloadLimit() const
{ {
return m_downloadLimit; return m_downloadLimit;;
} }
int TorrentImpl::uploadLimit() const int TorrentImpl::uploadLimit() const
@@ -1487,11 +1465,48 @@ bool TorrentImpl::isLSDDisabled() const
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd); 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 QBitArray TorrentImpl::pieces() const
{ {
return m_pieces; 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 qreal TorrentImpl::distributedCopies() const
{ {
return m_nativeStatus.distributed_copies; return m_nativeStatus.distributed_copies;
@@ -1737,6 +1752,12 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
m_nativeHandle.prioritize_pieces(piecePriorities); 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) 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() const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
@@ -1847,7 +1868,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
applyFirstLastPiecePriority(true); applyFirstLastPiecePriority(true);
m_maintenanceJob = MaintenanceJob::None; m_maintenanceJob = MaintenanceJob::None;
prepareResumeData(std::move(p)); prepareResumeData(p);
m_session->handleTorrentMetadataReceived(this); m_session->handleTorrentMetadataReceived(this);
} }
@@ -1856,6 +1877,16 @@ void TorrentImpl::reload()
{ {
try 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; lt::add_torrent_params p = m_ltAddTorrentParams;
p.flags |= lt::torrent_flags::update_subscribe p.flags |= lt::torrent_flags::update_subscribe
| lt::torrent_flags::override_trackers | lt::torrent_flags::override_trackers
@@ -1875,21 +1906,19 @@ void TorrentImpl::reload()
p.flags &= ~(lt::torrent_flags::auto_managed | lt::torrent_flags::paused); 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 = extensionData->status;
m_nativeStatus = static_cast<ExtensionData *>(m_nativeHandle.userdata())->status;
if (queuePos >= lt::queue_position_t {}) if (queuePos >= lt::queue_position_t {})
m_nativeHandle.queue_position_set(queuePos); m_nativeHandle.queue_position_set(queuePos);
m_nativeStatus.queue_position = 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(); updateState();
} }
catch (const lt::system_error &err) catch (const lt::system_error &err)
@@ -2034,7 +2063,7 @@ void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStora
} }
} }
void TorrentImpl::handleTorrentChecked() void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_checked_alert *p)
{ {
if (!hasMetadata()) if (!hasMetadata())
{ {
@@ -2077,7 +2106,7 @@ void TorrentImpl::handleTorrentChecked()
}); });
} }
void TorrentImpl::handleTorrentFinished() void TorrentImpl::handleTorrentFinishedAlert([[maybe_unused]] const lt::torrent_finished_alert *p)
{ {
m_hasMissingFiles = false; m_hasMissingFiles = false;
if (m_hasFinishedStatus) if (m_hasFinishedStatus)
@@ -2100,29 +2129,37 @@ void TorrentImpl::handleTorrentFinished()
m_hasFinishedStatus = true; m_hasFinishedStatus = true;
if (isMoveInProgress() || (m_renameCount > 0)) if (isMoveInProgress() || (m_renameCount > 0))
m_moveFinishedTriggers.enqueue([this] { m_session->handleTorrentFinished(this); }); m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
else else
m_session->handleTorrentFinished(this); 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. // 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 // 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. // 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()); Q_ASSERT(m_indexMap.isEmpty());
const auto isSeedMode = static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode); 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) if (isSeedMode)
m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode; m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
@@ -2161,20 +2198,15 @@ void TorrentImpl::handleSaveResumeData(lt::add_torrent_params params)
filePaths[i] = Path(it->second); filePaths[i] = Path(it->second);
} }
m_session->findIncompleteFiles(savePath(), downloadPath(), filePaths).then(this m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
, [this](const FileSearchResult &result)
{
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
endReceivedMetadataHandling(result.savePath, result.fileNames);
});
} }
else 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; decltype(params.have_pieces) havePieces;
@@ -2198,7 +2230,7 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
} }
// Update recent resume data // Update recent resume data
m_ltAddTorrentParams = std::move(params); m_ltAddTorrentParams = params;
if (needPreserveProgress) if (needPreserveProgress)
{ {
@@ -2214,7 +2246,7 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
// We shouldn't save upload_mode flag to allow torrent operate normally on next run // We shouldn't save upload_mode flag to allow torrent operate normally on next run
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode; m_ltAddTorrentParams.flags &= ~lt::torrent_flags::upload_mode;
LoadTorrentParams resumeData const LoadTorrentParams resumeData
{ {
.ltAddTorrentParams = m_ltAddTorrentParams, .ltAddTorrentParams = m_ltAddTorrentParams,
.name = m_name, .name = m_name,
@@ -2222,7 +2254,6 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
.tags = m_tags, .tags = m_tags,
.savePath = (!m_useAutoTMM ? m_savePath : Path()), .savePath = (!m_useAutoTMM ? m_savePath : Path()),
.downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()), .downloadPath = (!m_useAutoTMM ? m_downloadPath : Path()),
.comment = m_comment,
.contentLayout = m_contentLayout, .contentLayout = m_contentLayout,
.operatingMode = m_operatingMode, .operatingMode = m_operatingMode,
.useAutoTMM = m_useAutoTMM, .useAutoTMM = m_useAutoTMM,
@@ -2238,20 +2269,33 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params)
.sslParameters = m_sslParams .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 // Files were probably moved or storage isn't accessible
m_hasMissingFiles = true; 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); Q_ASSERT(fileIndex >= 0);
const Path newActualFilePath {QString::fromUtf8(p->new_name())};
const Path oldFilePath = m_filePaths.at(fileIndex); const Path oldFilePath = m_filePaths.at(fileIndex);
const Path newFilePath = makeUserPath(newActualFilePath); const Path newFilePath = makeUserPath(newActualFilePath);
@@ -2261,6 +2305,11 @@ void TorrentImpl::handleFileRenamed(const lt::file_index_t nativeFileIndex, cons
if (oldFilePath.data() == newFilePath.data()) if (oldFilePath.data() == newFilePath.data())
{ {
// Remove empty ".unwanted" folders // 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 oldActualParentPath = oldActualFilePath.parentPath();
const Path newActualParentPath = newActualFilePath.parentPath(); const Path newActualParentPath = newActualFilePath.parentPath();
if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME) if (newActualParentPath.filename() == UNWANTED_FOLDER_NAME)
@@ -2310,11 +2359,14 @@ void TorrentImpl::handleFileRenamed(const lt::file_index_t nativeFileIndex, cons
deferredRequestResumeData(); 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); 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; --m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
@@ -2322,12 +2374,12 @@ void TorrentImpl::handleFileRenameFailed(const lt::file_index_t nativeFileIndex)
deferredRequestResumeData(); deferredRequestResumeData();
} }
void TorrentImpl::handleFileCompleted(const lt::file_index_t nativeFileIndex) void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
{ {
if (m_maintenanceJob == MaintenanceJob::HandleMetadata) if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
return; return;
const int fileIndex = fileIndexFromNative(nativeFileIndex); const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0); Q_ASSERT(fileIndex >= 0);
m_completedFiles.setBit(fileIndex); m_completedFiles.setBit(fileIndex);
@@ -2355,13 +2407,22 @@ void TorrentImpl::handleFileCompleted(const lt::file_index_t nativeFileIndex)
} }
} }
void TorrentImpl::handleFileError(FileErrorInfo fileError) void TorrentImpl::handleFileErrorAlert(const lt::file_error_alert *p)
{ {
m_lastFileError = std::move(fileError); m_lastFileError = {p->error, p->op};
} }
void TorrentImpl::handleMetadataReceived() #ifdef QBT_USES_LIBTORRENT2
void TorrentImpl::handleFilePrioAlert(const lt::file_prio_alert *)
{ {
deferredRequestResumeData();
}
#endif
void TorrentImpl::handleMetadataReceivedAlert([[maybe_unused]] const lt::metadata_received_alert *p)
{
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
const InfoHash prevInfoHash = infoHash(); const InfoHash prevInfoHash = infoHash();
m_infoHash = InfoHash(m_nativeHandle.info_hashes()); m_infoHash = InfoHash(m_nativeHandle.info_hashes());
@@ -2373,6 +2434,12 @@ void TorrentImpl::handleMetadataReceived()
deferredRequestResumeData(); 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() void TorrentImpl::handleCategoryOptionsChanged()
{ {
if (m_useAutoTMM) if (m_useAutoTMM)
@@ -2395,6 +2462,57 @@ void TorrentImpl::handleUnwantedFolderToggled()
manageActualFilePaths(); 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() void TorrentImpl::manageActualFilePaths()
{ {
const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo(); const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
@@ -2442,11 +2560,6 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
return m_nativeHandle; 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) void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
{ {
if (hasMetadata()) if (hasMetadata())
@@ -2766,9 +2879,18 @@ nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
try try
{ {
[[maybe_unused]] const auto infoGuard = qScopeGuard([this] { m_ltAddTorrentParams.ti.reset(); }); #ifdef QBT_USES_LIBTORRENT2
m_ltAddTorrentParams.ti = info().nativeInfo(); const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
return lt::write_torrent_file(m_ltAddTorrentParams); 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) catch (const lt::system_error &err)
{ {
@@ -2780,7 +2902,7 @@ nonstd::expected<QByteArray, QString> TorrentImpl::exportToBuffer() const
{ {
const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent(); const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
if (!preparationResult) if (!preparationResult)
return nonstd::make_unexpected(preparationResult.error()); return preparationResult.get_unexpected();
// usually torrent size should be smaller than 1 MB, // usually torrent size should be smaller than 1 MB,
// however there are >100 MB v2/hybrid torrent files out in the wild // however there are >100 MB v2/hybrid torrent files out in the wild
@@ -2794,18 +2916,18 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
{ {
const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent(); const nonstd::expected<lt::entry, QString> preparationResult = exportTorrent();
if (!preparationResult) if (!preparationResult)
return nonstd::make_unexpected(preparationResult.error()); return preparationResult.get_unexpected();
const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value()); const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, preparationResult.value());
if (!saveResult) if (!saveResult)
return nonstd::make_unexpected(saveResult.error()); return saveResult.get_unexpected();
return {}; 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 try
{ {
@@ -2820,12 +2942,13 @@ QFuture<QList<PeerInfo>> TorrentImpl::fetchPeerInfo() const
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; 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 try
{ {
@@ -2839,12 +2962,13 @@ QFuture<QList<QUrl>> TorrentImpl::fetchURLSeeds() const
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; 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 try
{ {
@@ -2855,12 +2979,13 @@ QFuture<QList<int>> TorrentImpl::fetchPieceAvailability() const
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; 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 try
{ {
@@ -2879,12 +3004,13 @@ QFuture<QBitArray> TorrentImpl::fetchDownloadingPieces() const
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; 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)) if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
return {}; return {};
@@ -2918,7 +3044,8 @@ QFuture<QList<qreal>> TorrentImpl::fetchAvailableFileFractions() const
catch (const std::exception &) {} catch (const std::exception &) {}
return {}; return {};
}); }
, std::move(resultHandler));
} }
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities) void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
@@ -2929,7 +3056,7 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
Q_ASSERT(priorities.size() == filesCount()); Q_ASSERT(priorities.size() == filesCount());
// Reset 'm_hasSeedStatus' if needed in order to react again to // 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(); const QList<DownloadPriority> oldPriorities = filePriorities();
for (int i = 0; i < oldPriorities.size(); ++i) for (int i = 0; i < oldPriorities.size(); ++i)
{ {
@@ -2958,17 +3085,47 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
manageActualFilePaths(); manageActualFilePaths();
} }
template <typename Func> QList<qreal> TorrentImpl::availableFileFractions() const
QFuture<std::invoke_result_t<Func>> TorrentImpl::invokeAsync(Func &&func) const
{ {
QPromise<std::invoke_result_t<Func>> promise; Q_ASSERT(hasMetadata());
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();
});
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) Q_DISABLE_COPY_MOVE(TorrentImpl)
public: 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; ~TorrentImpl() override;
bool isValid() const; bool isValid() const;
@@ -106,7 +107,6 @@ namespace BitTorrent
QDateTime creationDate() const override; QDateTime creationDate() const override;
QString creator() const override; QString creator() const override;
QString comment() const override; QString comment() const override;
void setComment(const QString &comment) override;
bool isPrivate() const override; bool isPrivate() const override;
qlonglong totalSize() const override; qlonglong totalSize() const override;
qlonglong wantedSize() const override; qlonglong wantedSize() const override;
@@ -203,7 +203,10 @@ namespace BitTorrent
bool isDHTDisabled() const override; bool isDHTDisabled() const override;
bool isPEXDisabled() const override; bool isPEXDisabled() const override;
bool isLSDDisabled() const override; bool isLSDDisabled() const override;
QList<PeerInfo> peers() const override;
QBitArray pieces() const override; QBitArray pieces() const override;
QBitArray downloadingPieces() const override;
QList<int> pieceAvailability() const override;
qreal distributedCopies() const override; qreal distributedCopies() const override;
qreal maxRatio() const override; qreal maxRatio() const override;
int maxSeedingTime() const override; int maxSeedingTime() const override;
@@ -217,6 +220,7 @@ namespace BitTorrent
int connectionsCount() const override; int connectionsCount() const override;
int connectionsLimit() const override; int connectionsLimit() const override;
qlonglong nextAnnounce() const override; qlonglong nextAnnounce() const override;
QList<qreal> availableFileFractions() const override;
void setName(const QString &name) override; void setName(const QString &name) override;
void setSequentialDownload(bool enable) override; void setSequentialDownload(bool enable) override;
@@ -254,29 +258,19 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override; nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) const override; nonstd::expected<void, QString> exportToFile(const Path &path) const override;
QFuture<QList<PeerInfo>> fetchPeerInfo() const override; void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override;
QFuture<QList<QUrl>> fetchURLSeeds() const override; void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override;
QFuture<QList<int>> fetchPieceAvailability() const override; void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override;
QFuture<QBitArray> fetchDownloadingPieces() const override; void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
QFuture<QList<qreal>> fetchAvailableFileFractions() const override; void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override;
bool needSaveResumeData() const; bool needSaveResumeData() const;
// Session interface // Session interface
lt::torrent_handle nativeHandle() const; 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 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 handleQueueingModeChanged();
void handleCategoryOptionsChanged(); void handleCategoryOptionsChanged();
void handleAppendExtensionToggled(); void handleAppendExtensionToggled();
@@ -284,6 +278,7 @@ namespace BitTorrent
void requestResumeData(lt::resume_data_flags_t flags = {}); void requestResumeData(lt::resume_data_flags_t flags = {});
void deferredRequestResumeData(); void deferredRequestResumeData();
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob); 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); TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
void resetTrackerEntryStatuses(); void resetTrackerEntryStatuses();
@@ -296,6 +291,23 @@ namespace BitTorrent
void updateProgress(); void updateProgress();
void updateState(); 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; bool isMoveInProgress() const;
void setAutoManaged(bool enable); void setAutoManaged(bool enable);
@@ -308,16 +320,17 @@ namespace BitTorrent
void manageActualFilePaths(); void manageActualFilePaths();
void applyFirstLastPiecePriority(bool enabled); 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 endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames);
void reload(); void reload();
nonstd::expected<lt::entry, QString> exportTorrent() const; nonstd::expected<lt::entry, QString> exportTorrent() const;
template <typename Func> template <typename Func, typename Callback>
QFuture<std::invoke_result_t<Func>> invokeAsync(Func &&func) const; void invokeAsync(Func func, Callback resultHandler) const;
SessionImpl *const m_session = nullptr; SessionImpl *const m_session = nullptr;
lt::session *m_nativeSession = nullptr;
lt::torrent_handle m_nativeHandle; lt::torrent_handle m_nativeHandle;
mutable lt::torrent_status m_nativeStatus; mutable lt::torrent_status m_nativeStatus;
TorrentState m_state = TorrentState::Unknown; TorrentState m_state = TorrentState::Unknown;
@@ -374,7 +387,7 @@ namespace BitTorrent
bool m_unchecked = false; bool m_unchecked = false;
mutable lt::add_torrent_params m_ltAddTorrentParams; lt::add_torrent_params m_ltAddTorrentParams;
int m_downloadLimit = 0; int m_downloadLimit = 0;
int m_uploadLimit = 0; int m_uploadLimit = 0;

View File

@@ -380,7 +380,7 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{ {
// Reached max size, remove a random torrent // Reached max size, remove a random torrent
if (m_torrents.size() >= MAX_TORRENTS) 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); m_torrents[announceReq.torrentID].setPeer(announceReq.peer);

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -41,6 +41,7 @@ namespace BitTorrent
{ {
NotContacted = 1, NotContacted = 1,
Working = 2, Working = 2,
Updating = 3,
NotWorking = 4, NotWorking = 4,
TrackerError = 5, TrackerError = 5,
Unreachable = 6 Unreachable = 6
@@ -51,7 +52,6 @@ namespace BitTorrent
QString name {}; QString name {};
int btVersion = 1; int btVersion = 1;
bool isUpdating = false;
TrackerEndpointState state = TrackerEndpointState::NotContacted; TrackerEndpointState state = TrackerEndpointState::NotContacted;
QString message {}; QString message {};
@@ -69,7 +69,6 @@ namespace BitTorrent
QString url {}; QString url {};
int tier = 0; int tier = 0;
bool isUpdating = false;
TrackerEndpointState state = TrackerEndpointState::NotContacted; TrackerEndpointState state = TrackerEndpointState::NotContacted;
QString message {}; QString message {};

View File

@@ -155,11 +155,7 @@ void Connection::read()
sendResponse(resp); sendResponse(resp);
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_receivedData.slice(result.frameSize);
#else
m_receivedData.remove(0, result.frameSize); m_receivedData.remove(0, result.frameSize);
#endif
} }
break; break;

View File

@@ -31,13 +31,12 @@
#include "requestparser.h" #include "requestparser.h"
#include <algorithm> #include <algorithm>
#include <optional>
#include <utility> #include <utility>
#include <QByteArrayList>
#include <QByteArrayView> #include <QByteArrayView>
#include <QDebug> #include <QDebug>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringList>
#include <QUrl> #include <QUrl>
#include <QUrlQuery> #include <QUrlQuery>
@@ -60,19 +59,21 @@ namespace
return in; return in;
} }
std::optional<QStringPair> parseHeaderLine(const QByteArrayView line) bool parseHeaderLine(const QStringView line, HeaderMap &out)
{ {
// [rfc7230] 3.2. Header Fields // [rfc7230] 3.2. Header Fields
const int i = line.indexOf(u':'); const int i = line.indexOf(u':');
if (i <= 0) if (i <= 0)
{ {
qWarning() << Q_FUNC_INFO << "invalid http header:" << line; qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
return std::nullopt; return false;
} }
const QString name = QString::fromLatin1(line.first(i).trimmed()).toLower(); const QString name = line.left(i).trimmed().toString().toLower();
const QString value = QString::fromLatin1(line.sliced(i + 1).trimmed()); const QString value = line.mid(i + 1).trimmed().toString();
return {{name, value}}; out[name] = value;
return true;
} }
} }
@@ -92,7 +93,7 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArrayView data)
return {ParseStatus::Incomplete, Request(), 0}; return {ParseStatus::Incomplete, Request(), 0};
} }
const QByteArrayView httpHeaders = data.first(headerEnd); const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
if (!parseStartLines(httpHeaders)) if (!parseStartLines(httpHeaders))
{ {
qWarning() << Q_FUNC_INFO << "header parsing error"; qWarning() << Q_FUNC_INFO << "header parsing error";
@@ -151,40 +152,36 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArrayView data)
return {ParseStatus::BadMethod, m_request, 0}; return {ParseStatus::BadMethod, m_request, 0};
} }
bool RequestParser::parseStartLines(const QByteArrayView data) bool RequestParser::parseStartLines(const QStringView data)
{ {
// we don't handle malformed request which uses `LF` for newline // we don't handle malformed request which uses `LF` for newline
const QList<QByteArrayView> lines = splitToViews(data, CRLF, Qt::SkipEmptyParts); const QList<QStringView> lines = data.split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
// [rfc7230] 3.2.2. Field Order // [rfc7230] 3.2.2. Field Order
QByteArrayList requestLines; QStringList requestLines;
for (const auto &line : lines) for (const auto &line : lines)
{ {
if (QChar::fromLatin1(line.at(0)).isSpace() && !requestLines.isEmpty()) if (line.at(0).isSpace() && !requestLines.isEmpty())
{ {
// continuation of previous line // continuation of previous line
requestLines.last() += line; requestLines.last() += line;
} }
else else
{ {
requestLines += line.toByteArray(); requestLines += line.toString();
} }
} }
if (requestLines.isEmpty()) if (requestLines.isEmpty())
return false; return false;
if (!parseRequestLine(QString::fromLatin1(requestLines[0]))) if (!parseRequestLine(requestLines[0]))
return false; return false;
for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i) for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i)
{ {
const std::optional<QStringPair> header = parseHeaderLine(*i); if (!parseHeaderLine(*i, m_request.headers))
if (!header.has_value())
return false; return false;
const auto [name, value] = header.value();
m_request.headers[name] = value;
} }
return true; return true;
@@ -207,15 +204,15 @@ bool RequestParser::parseRequestLine(const QString &line)
m_request.method = match.captured(1); m_request.method = match.captured(1);
// Request Target // Request Target
const QByteArray url {match.capturedView(2).toLatin1()}; const QByteArray url {match.captured(2).toLatin1()};
const int sepPos = url.indexOf('?'); 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))); m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(asQByteArray(pathComponent)));
if (sepPos >= 0) 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 // [rfc3986] 2.4 When to Encode or Decode
// URL components should be separated before percent-decoding // URL components should be separated before percent-decoding
@@ -224,8 +221,8 @@ bool RequestParser::parseRequestLine(const QString &line)
const int eqCharPos = param.indexOf('='); const int eqCharPos = param.indexOf('=');
if (eqCharPos <= 0) continue; // ignores params without name if (eqCharPos <= 0) continue; // ignores params without name
const QByteArrayView nameComponent = param.first(eqCharPos); const QByteArrayView nameComponent = param.mid(0, eqCharPos);
const QByteArrayView valueComponent = param.sliced(eqCharPos + 1); const QByteArrayView valueComponent = param.mid(eqCharPos + 1);
const QString paramName = QString::fromUtf8( const QString paramName = QString::fromUtf8(
QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' ')); QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' '));
const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' '); const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
@@ -273,7 +270,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
return false; 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()) if (delimiter.isEmpty())
{ {
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!"; qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
@@ -282,7 +279,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
// split data by "dash-boundary" // split data by "dash-boundary"
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF; const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter); QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
if (multipart.isEmpty()) if (multipart.isEmpty())
{ {
qWarning() << Q_FUNC_INFO << "multipart empty"; qWarning() << Q_FUNC_INFO << "multipart empty";
@@ -313,23 +310,17 @@ bool RequestParser::parseFormData(const QByteArrayView data)
return false; return false;
} }
const QByteArrayView headers = data.first(eohPos); const QString headers = QString::fromLatin1(data.mid(0, eohPos));
const QByteArrayView payload = viewWithoutEndingWith(data.sliced((eohPos + EOH.size())), CRLF); const QByteArrayView payload = viewWithoutEndingWith(data.mid((eohPos + EOH.size()), data.size()), CRLF);
HeaderMap headersMap; HeaderMap headersMap;
const QList<QByteArrayView> headerLines = splitToViews(headers, CRLF, Qt::SkipEmptyParts); const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
for (const auto &line : headerLines) for (const auto &line : headerLines)
{ {
const std::optional<QStringPair> header = parseHeaderLine(line); if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive))
if (!header.has_value())
return false;
const auto [name, value] = header.value();
if (name == HEADER_CONTENT_DISPOSITION)
{ {
// extract out filename & name // extract out filename & name
const QList<QByteArrayView> directives = splitToViews(line, ";", Qt::SkipEmptyParts); const QList<QStringView> directives = line.split(u';', Qt::SkipEmptyParts);
for (const auto &directive : directives) for (const auto &directive : directives)
{ {
@@ -337,14 +328,15 @@ bool RequestParser::parseFormData(const QByteArrayView data)
if (idx < 0) if (idx < 0)
continue; continue;
const QString name = QString::fromLatin1(directive.first(idx).trimmed()).toLower(); const QString name = directive.left(idx).trimmed().toString().toLower();
const QString value = QString::fromLatin1(unquote(directive.sliced(idx + 1).trimmed())); const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
headersMap[name] = value; headersMap[name] = value;
} }
} }
else else
{ {
headersMap[name] = value; if (!parseHeaderLine(line, headersMap))
return false;
} }
} }

View File

@@ -61,7 +61,7 @@ namespace Http
RequestParser() = default; RequestParser() = default;
ParseResult doParse(QByteArrayView data); ParseResult doParse(QByteArrayView data);
bool parseStartLines(QByteArrayView data); bool parseStartLines(QStringView data);
bool parseRequestLine(const QString &line); bool parseRequestLine(const QString &line);
bool parsePostMessage(QByteArrayView data); bool parsePostMessage(QByteArrayView data);

View File

@@ -60,7 +60,7 @@ namespace Http
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s; inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s; inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s; inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
inline const QString HEADER_X_FORWARDED_PROTO = u"x-forwarded-proto"_s; inline const QString HEADER_X_FORWARDED_PROTO = u"X-forwarded-proto"_s;
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s; inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s; inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;

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)); const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data));
if (ipRegexMatch.hasMatch()) 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; qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
const QHostAddress newIp {ipStr}; QHostAddress newIp(ipStr);
if (!newIp.isNull()) if (!newIp.isNull())
{ {
if (m_lastIP != newIp) if (m_lastIP != newIp)

View File

@@ -279,13 +279,13 @@ QString Net::DownloadHandlerImpl::errorCodeToString(const QNetworkReply::Network
case QNetworkReply::ProxyAuthenticationRequiredError: case QNetworkReply::ProxyAuthenticationRequiredError:
return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered"); return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
case QNetworkReply::ContentAccessDenied: 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: case QNetworkReply::ContentOperationNotPermittedError:
return tr("The operation requested on the remote content is not permitted"); return tr("The operation requested on the remote content is not permitted");
case QNetworkReply::ContentNotFoundError: case QNetworkReply::ContentNotFoundError:
return tr("The remote content was not found at the server (404)"); return tr("The remote content was not found at the server (404)");
case QNetworkReply::AuthenticationRequiredError: 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: case QNetworkReply::ProtocolUnknownError:
return tr("The Network Access API cannot honor the request because the protocol is not known"); return tr("The Network Access API cannot honor the request because the protocol is not known");
case QNetworkReply::ProtocolInvalidOperationError: case QNetworkReply::ProtocolInvalidOperationError:

View File

@@ -265,37 +265,38 @@ void Net::DownloadManager::applyProxySettings()
const auto *proxyManager = ProxyConfigurationManager::instance(); const auto *proxyManager = ProxyConfigurationManager::instance();
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration(); 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: qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
case Net::ProxyType::SOCKS4: m_proxy.setType(QNetworkProxy::Socks5Proxy);
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy); }
break; else
{
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
m_proxy.setType(QNetworkProxy::HttpProxy);
}
case Net::ProxyType::HTTP: m_proxy.setHostName(proxyConfig.ip);
m_proxy = QNetworkProxy( m_proxy.setPort(proxyConfig.port);
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;
case Net::ProxyType::SOCKS5: // Authentication?
m_proxy = QNetworkProxy( if (proxyConfig.authEnabled)
QNetworkProxy::Socks5Proxy {
, proxyConfig.ip qDebug("Proxy requires authentication, authenticating...");
, proxyConfig.port m_proxy.setUser(proxyConfig.username);
, (proxyConfig.authEnabled ? proxyConfig.username : QString()) m_proxy.setPassword(proxyConfig.password);
, (proxyConfig.authEnabled ? proxyConfig.password : QString())); }
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability) if (proxyConfig.hostnameLookupEnabled)
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability)); m_proxy.setCapabilities(m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability);
break; else
}; m_proxy.setCapabilities(m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability);
} }
void Net::DownloadManager::processWaitingJobs(const ServiceID &serviceID) 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 // Encode the body in base64
QString crlfBody = body; QString crlfBody = body;
const QByteArray b = crlfBody.replace(u"\n"_s, u"\r\n"_s).toUtf8().toBase64(); 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_message += b.mid(i, 78);
m_from = from; m_from = from;
m_rcpt = to; m_rcpt = to;
@@ -189,12 +190,8 @@ void Smtp::readyRead()
{ {
const int pos = m_buffer.indexOf("\r\n"); const int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition if (pos < 0) return; // Loop exit condition
const QByteArray line = m_buffer.first(pos); const QByteArray line = m_buffer.left(pos);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_buffer.slice(pos + 2);
#else
m_buffer.remove(0, (pos + 2)); m_buffer.remove(0, (pos + 2));
#endif
qDebug() << "Response line:" << line; qDebug() << "Response line:" << line;
// Extract response code // Extract response code
const QByteArray code = line.left(3); const QByteArray code = line.left(3);

View File

@@ -94,13 +94,7 @@ bool Path::isValid() const
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QStringView view = m_pathStr; QStringView view = m_pathStr;
if (hasDriveLetter(view)) if (hasDriveLetter(view))
{ view = view.mid(3);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
view.slice(3);
#else
view = view.sliced(3);
#endif
}
// \\37 is using base-8 number system // \\37 is using base-8 number system
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s}; const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
@@ -153,9 +147,9 @@ Path Path::rootItem() const
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
// should be `c:/` instead of `c:` // should be `c:/` instead of `c:`
if ((slashIndex == 2) && hasDriveLetter(m_pathStr)) if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return createUnchecked(m_pathStr.first(slashIndex + 1)); return createUnchecked(m_pathStr.left(slashIndex + 1));
#endif #endif
return createUnchecked(m_pathStr.first(slashIndex)); return createUnchecked(m_pathStr.left(slashIndex));
} }
Path Path::parentPath() const Path Path::parentPath() const
@@ -173,9 +167,9 @@ Path Path::parentPath() const
// should be `c:/` instead of `c:` // should be `c:/` instead of `c:`
// Windows "drive letter" is limited to one alphabet // Windows "drive letter" is limited to one alphabet
if ((slashIndex == 2) && hasDriveLetter(m_pathStr)) 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 #endif
return createUnchecked(m_pathStr.first(slashIndex)); return createUnchecked(m_pathStr.left(slashIndex));
} }
QString Path::filename() const QString Path::filename() const
@@ -184,7 +178,7 @@ QString Path::filename() const
if (slashIndex == -1) if (slashIndex == -1)
return m_pathStr; return m_pathStr;
return m_pathStr.sliced(slashIndex + 1); return m_pathStr.mid(slashIndex + 1);
} }
QString Path::extension() const QString Path::extension() const
@@ -194,9 +188,9 @@ QString Path::extension() const
return (u"." + suffix); return (u"." + suffix);
const int slashIndex = m_pathStr.lastIndexOf(u'/'); 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); 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 bool Path::hasExtension(const QStringView ext) const
@@ -299,7 +293,7 @@ Path Path::commonPath(const Path &left, const Path &right)
if (commonItemsCount > 0) if (commonItemsCount > 0)
commonPathSize += (commonItemsCount - 1); // size of intermediate separators 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) Path Path::findRootFolder(const PathList &filePaths)
@@ -328,13 +322,7 @@ void Path::stripRootFolder(PathList &filePaths)
return; return;
for (Path &filePath : filePaths) 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)); filePath.m_pathStr.remove(0, (commonRootFolder.m_pathStr.size() + 1));
#endif
}
} }
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder) 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); 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 bool Preferences::getHideZeroValues() const
{ {
return value(u"Preferences/General/HideZeroValues"_s, false); 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); 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 bool Preferences::isStatusbarExternalIPDisplayed() const
{ {
return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false); return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false);

View File

@@ -113,18 +113,12 @@ public:
void showSpeedInTitleBar(bool show); void showSpeedInTitleBar(bool show);
bool useAlternatingRowColors() const; bool useAlternatingRowColors() const;
void setAlternatingRowColors(bool b); void setAlternatingRowColors(bool b);
bool useTorrentStatesColors() const;
void setUseTorrentStatesColors(bool value);
bool getProgressBarFollowsTextColor() const;
void setProgressBarFollowsTextColor(bool value);
bool getHideZeroValues() const; bool getHideZeroValues() const;
void setHideZeroValues(bool b); void setHideZeroValues(bool b);
int getHideZeroComboValues() const; int getHideZeroComboValues() const;
void setHideZeroComboValues(int n); void setHideZeroComboValues(int n);
bool isStatusbarDisplayed() const; bool isStatusbarDisplayed() const;
void setStatusbarDisplayed(bool displayed); void setStatusbarDisplayed(bool displayed);
bool isStatusbarFreeDiskSpaceDisplayed() const;
void setStatusbarFreeDiskSpaceDisplayed(bool displayed);
bool isStatusbarExternalIPDisplayed() const; bool isStatusbarExternalIPDisplayed() const;
void setStatusbarExternalIPDisplayed(bool displayed); void setStatusbarExternalIPDisplayed(bool displayed);
bool isToolbarDisplayed() const; bool isToolbarDisplayed() const;

View File

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

View File

@@ -43,7 +43,6 @@
#include "base/addtorrentmanager.h" #include "base/addtorrentmanager.h"
#include "base/asyncfilestorage.h" #include "base/asyncfilestorage.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h" #include "base/bittorrent/torrentdescriptor.h"
#include "base/global.h" #include "base/global.h"
@@ -118,7 +117,7 @@ AutoDownloader::AutoDownloader(IApplication *app)
m_fileStorage->moveToThread(m_ioThread.get()); m_fileStorage->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileStorage, &AsyncFileStorage::deleteLater); 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") LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
.arg(fileName.toString(), errorString), Log::CRITICAL); .arg(fileName.toString(), errorString), Log::CRITICAL);

View File

@@ -37,6 +37,7 @@
#include <QSharedPointer> #include <QSharedPointer>
#include "base/applicationcomponent.h" #include "base/applicationcomponent.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/exceptions.h" #include "base/exceptions.h"
#include "base/settingvalue.h" #include "base/settingvalue.h"
#include "base/utils/thread.h" #include "base/utils/thread.h"
@@ -47,11 +48,6 @@ class Application;
class AsyncFileStorage; class AsyncFileStorage;
struct ProcessingJob; struct ProcessingJob;
namespace BitTorrent
{
struct AddTorrentError;
}
namespace RSS namespace RSS
{ {
class Article; class Article;

View File

@@ -184,10 +184,14 @@ QString computeEpisodeName(const QString &article)
for (int i = 1; i <= match.lastCapturedIndex(); ++i) for (int i = 1; i <= match.lastCapturedIndex(); ++i)
{ {
const QString cap = match.captured(i); const QString cap = match.captured(i);
if (cap.isEmpty()) if (cap.isEmpty())
continue; 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'); return ret.join(u'x');
} }
@@ -289,26 +293,20 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (!matcher.hasMatch()) if (!matcher.hasMatch())
return false; return false;
const QStringView season {matcher.capturedView(1)}; const QString season {matcher.captured(1)};
const QList<QStringView> episodes {matcher.capturedView(2).split(u';')}; const QStringList episodes {matcher.captured(2).split(u';')};
const int seasonOurs {season.toInt()}; const int seasonOurs {season.toInt()};
for (QStringView episode : episodes) for (QString episode : episodes)
{ {
if (episode.isEmpty()) if (episode.isEmpty())
continue; continue;
// We need to trim leading zeroes, but if it's all zeros then we want episode zero. // 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')) while ((episode.size() > 1) && episode.startsWith(u'0'))
{ episode = episode.right(episode.size() - 1);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
episode.slice(1);
#else
episode = episode.sliced(1);
#endif
}
if (episode.contains(u'-')) if (episode.indexOf(u'-') != -1)
{ // Range detected { // Range detected
const QString partialPattern1 {u"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"_s}; 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}; 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) if (matched)
{ {
const int seasonTheirs {matcher.capturedView(1).toInt()}; const int seasonTheirs {matcher.captured(1).toInt()};
const int episodeTheirs {matcher.capturedView(2).toInt()}; const int episodeTheirs {matcher.captured(2).toInt()};
if (episode.endsWith(u'-')) if (episode.endsWith(u'-'))
{ // Infinite range { // 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)) if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
return true; return true;
} }
else else
{ // Normal range { // Normal range
const QList<QStringView> range {episode.split(u'-')}; const QStringList range {episode.split(u'-')};
Q_ASSERT(range.size() == 2); 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 episodeOursFirst {range.first().toInt()};
const int episodeOursLast {range.last().toInt()}; const int episodeOursLast {range.last().toInt()};
if (episodeOursFirst > episodeOursLast)
continue; // Ignore this subrule completely
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs))) if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
return true; return true;
} }

View File

@@ -1,7 +1,7 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker * 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 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org> * Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
* *
@@ -56,22 +56,19 @@
const QString KEY_UID = u"uid"_s; const QString KEY_UID = u"uid"_s;
const QString KEY_URL = u"url"_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_TITLE = u"title"_s;
const QString KEY_LASTBUILDDATE = u"lastBuildDate"_s; const QString KEY_LASTBUILDDATE = u"lastBuildDate"_s;
const QString KEY_ISLOADING = u"isLoading"_s; const QString KEY_ISLOADING = u"isLoading"_s;
const QString KEY_HASERROR = u"hasError"_s; const QString KEY_HASERROR = u"hasError"_s;
const QString KEY_ARTICLES = u"articles"_s; const QString KEY_ARTICLES = u"articles"_s;
using namespace std::chrono_literals;
using namespace RSS; 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) : Item(path)
, m_session {session} , m_session(session)
, m_uid {uid} , m_uid(uid)
, m_url {url} , m_url(url)
, m_refreshInterval {refreshInterval}
{ {
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex()); const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
m_dataFileName = Path(uidHex + u".json"); m_dataFileName = Path(uidHex + u".json");
@@ -330,9 +327,9 @@ bool Feed::addArticle(const QVariantHash &articleData)
// Insertion sort // Insertion sort
const int maxArticles = m_session->maxArticlesPerFeed(); const int maxArticles = m_session->maxArticlesPerFeed();
const auto lowerBound = std::lower_bound(m_articlesByDate.cbegin(), m_articlesByDate.cend() const auto lowerBound = std::lower_bound(m_articlesByDate.begin(), m_articlesByDate.end()
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan); , articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
if ((lowerBound - m_articlesByDate.cbegin()) >= maxArticles) if ((lowerBound - m_articlesByDate.begin()) >= maxArticles)
return false; // we reach max articles return false; // we reach max articles
auto *article = new Article(this, articleData); auto *article = new Article(this, articleData);
@@ -465,20 +462,6 @@ Path Feed::iconPath() const
return m_iconPath; 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) void Feed::setURL(const QString &url)
{ {
const QString oldURL = m_url; const QString oldURL = m_url;
@@ -491,8 +474,6 @@ QJsonValue Feed::toJsonValue(const bool withData) const
QJsonObject jsonObj; QJsonObject jsonObj;
jsonObj.insert(KEY_UID, uid().toString()); jsonObj.insert(KEY_UID, uid().toString());
jsonObj.insert(KEY_URL, url()); jsonObj.insert(KEY_URL, url());
if (refreshInterval() > 0s)
jsonObj.insert(KEY_REFRESHINTERVAL, static_cast<qint64>(refreshInterval().count()));
if (withData) if (withData)
{ {

View File

@@ -1,7 +1,7 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker * 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 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org> * Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
* *
@@ -31,8 +31,6 @@
#pragma once #pragma once
#include <chrono>
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QBasicTimer> #include <QBasicTimer>
#include <QHash> #include <QHash>
@@ -70,7 +68,7 @@ namespace RSS
friend class Session; 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; ~Feed() override;
public: public:
@@ -89,9 +87,6 @@ namespace RSS
Article *articleByGUID(const QString &guid) const; Article *articleByGUID(const QString &guid) const;
Path iconPath() const; Path iconPath() const;
std::chrono::seconds refreshInterval() const;
void setRefreshInterval(std::chrono::seconds refreshInterval);
QJsonValue toJsonValue(bool withData = false) const override; QJsonValue toJsonValue(bool withData = false) const override;
signals: signals:
@@ -99,7 +94,6 @@ namespace RSS
void titleChanged(Feed *feed = nullptr); void titleChanged(Feed *feed = nullptr);
void stateChanged(Feed *feed = nullptr); void stateChanged(Feed *feed = nullptr);
void urlChanged(const QString &oldURL); void urlChanged(const QString &oldURL);
void refreshIntervalChanged(std::chrono::seconds oldRefreshInterval);
private slots: private slots:
void handleSessionProcessingEnabledChanged(bool enabled); void handleSessionProcessingEnabledChanged(bool enabled);
@@ -129,7 +123,6 @@ namespace RSS
Private::FeedSerializer *m_serializer = nullptr; Private::FeedSerializer *m_serializer = nullptr;
const QUuid m_uid; const QUuid m_uid;
QString m_url; QString m_url;
std::chrono::seconds m_refreshInterval;
QString m_title; QString m_title;
QString m_lastBuildDate; QString m_lastBuildDate;
bool m_hasError = false; bool m_hasError = false;

View File

@@ -97,7 +97,7 @@ QStringList Item::expandPath(const QString &path)
int index = 0; int index = 0;
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0) while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
{ {
result << path.first(index); result << path.left(index);
++index; ++index;
} }
result << path; result << path;
@@ -108,11 +108,11 @@ QStringList Item::expandPath(const QString &path)
QString Item::parentPath(const QString &path) QString Item::parentPath(const QString &path)
{ {
const int pos = path.lastIndexOf(Item::PathSeparator); 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) QString Item::relativeName(const QString &path)
{ {
const int pos = path.lastIndexOf(Item::PathSeparator); int pos;
return (pos >= 0) ? path.sliced(pos + 1) : path; 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. * 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> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or

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