1
mirror of https://github.com/qbittorrent/qBittorrent synced 2025-10-09 18:32:15 +02:00

Compare commits

...

21 Commits

Author SHA1 Message Date
sledgehammer999
2bbfd317ce Sync translations from Transifex and run lupdate 2023-06-18 01:37:12 +03:00
Chocobo1
f6b58f36e2 WebUI: set Cross Origin Opener Policy to same-origin
This separates browsing context for different origin sites and prevents
leaking data from it.
This header is only present when using built-in WebUI. Alternative WebUI
is not affected.
https://web.dev/why-coop-coep/#coop

PR #19157.
2023-06-14 13:38:48 +08:00
Chocobo1
79ca2e145f Don't read unlimited data from files
It now guards against reading infinite files such as `/dev/zero`.
And most readings are bound with a (lax) limit.
As a side effect, more checking are done when reading a file and
overall the reading procedure is more robust.

PR #19095.
2023-06-14 13:38:19 +08:00
Chocobo1
81bc910d68 Provide context to translation strings
PR #19120.
2023-06-12 14:03:12 +08:00
Vort
ff5d02bcf2 Make I2P session options configurable
PR #19079.
Closes #18980.
2023-06-06 08:35:40 +03:00
tearfur
2e87e6e0df Use hostname instead of domain name in tracker filter list
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>

PR #19062.
Closes #19035.
2023-06-05 14:57:37 +03:00
Vladimir Golovnev
a5e8af5070 Allow to assign priority to RSS download rule
PR #19000.
2023-06-05 14:55:41 +03:00
Vladimir Golovnev
cf415dd7fe Allow to disable confirmation of Pause/Resume All
PR #19067.
Closes #18155.
2023-06-04 08:57:14 +03:00
Chocobo1
83e6afcb71 Merge pull request #19069 from Chocobo1/sort
WebUI: use natural sort on tracker list
2023-06-04 12:52:29 +08:00
Chocobo1
62d96c068a Remove SGML parser
This library is unmaintained, outdated and plugin authors are encouraged to use html.parser
from Python Standard Library instead.

https://docs.python.org/3/library/html.parser.html

PR #19068.
2023-06-04 12:52:06 +08:00
xavier2k6
040c3c7ef8 Sync "expected lite" with upstream
PR #19049.
2023-06-03 17:42:57 +03:00
Raymond Ha
3ef8726083 WebUI: Set Connection status and Speed limits tooltips
PR #19052.
Fixes #18958.
2023-06-03 17:39:58 +03:00
Chocobo1
dad9157d84 Don't overwrite original variable 2023-06-02 18:12:01 +08:00
Chocobo1
5cea69472f Use natural sort 2023-06-02 17:44:17 +08:00
ttys3
b1492bcd7d WebUI: Show only hosts in tracker filter list
PR #18190.
2023-06-02 17:36:33 +08:00
sledgehammer999
d571ab2be1 Update AppVeyor config
The config needs some updating to accommodate the new structure.

PR #19030.
2023-06-02 17:02:31 +08:00
Vladimir Golovnev
4550469bb9 Fix incorrect height of Filter line edit
PR #19058.
2023-06-02 11:47:53 +03:00
Vladimir Golovnev
160af4feef Show I2P peer addresses
PR #18845.
2023-06-01 17:16:03 +03:00
Priit Uring
b27e839405 Sync flag icons with upstream
PR #19027.
2023-06-01 06:49:09 +03:00
sledgehammer999
ecc08dee09 Bump to 4.6.0beta1 2023-05-29 16:03:44 +03:00
Chocobo1
11ac4e7620 GHA CI: upload macOS bundles
Hopefully those bundles will be runnable on users machine.

PR #19023.
2023-05-29 12:24:12 +08:00
234 changed files with 78743 additions and 81162 deletions

View File

@@ -37,8 +37,6 @@ install:
RMDIR /S /Q "%CACHE_DIR%" & MKDIR "%CACHE_DIR%" &&
appveyor DownloadFile "%QBT_LIB_URL%" -FileName "c:\qbt_lib.7z" && 7z x "c:\qbt_lib.7z" -o"%CACHE_DIR%" > nul &&
COPY "c:\version_new" "%CACHE_DIR%\version")
# Qt stay compressed in cache
- 7z x "%CACHE_DIR%\qt5_64.7z" -o"c:\qbt" > nul
before_build:
# setup env
@@ -47,6 +45,7 @@ before_build:
# setup project
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
# workarounds
- MKDIR "c:\qbt"
- MKLINK /J "c:\qbt\base" "%CACHE_DIR%\base"
build_script:
@@ -69,8 +68,11 @@ after_build:
- COPY src\release\qbittorrent.exe upload
- COPY src\release\qbittorrent.pdb upload
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.pdb" upload
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\lib\torrent-rasterbar.dll" upload
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.pdb" upload
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.dll" upload
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.pdb" upload
- COPY "%CACHE_DIR%\base\lib\zlib1.dll" upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Core.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Gui.dll upload

View File

@@ -74,6 +74,7 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
@@ -124,7 +125,17 @@ jobs:
- name: Prepare build artifacts
run: |
# create .dmg
appName="qbittorrent"
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
appName="qbittorrent-nox"
fi
pushd build
macdeployqt "$appName.app" -dmg -no-strip
popd
# prepare upload folder
mkdir upload
cp "build/$appName.dmg" upload
mkdir upload/cmake
cp build/compile_commands.json upload/cmake
mkdir upload/cmake/libtorrent
@@ -133,5 +144,5 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-info_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: upload

20
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0alpha1.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0beta1.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -611,8 +611,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.6.0alpha1'
PACKAGE_STRING='qbittorrent v4.6.0alpha1'
PACKAGE_VERSION='v4.6.0beta1'
PACKAGE_STRING='qbittorrent v4.6.0beta1'
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
PACKAGE_URL='https://www.qbittorrent.org/'
@@ -1329,7 +1329,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures qbittorrent v4.6.0alpha1 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.6.0beta1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1400,7 +1400,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.6.0alpha1:";;
short | recursive ) echo "Configuration of qbittorrent v4.6.0beta1:";;
esac
cat <<\_ACEOF
@@ -1533,7 +1533,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
qbittorrent configure v4.6.0alpha1
qbittorrent configure v4.6.0beta1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1648,7 +1648,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by qbittorrent $as_me v4.6.0alpha1, which was
It was created by qbittorrent $as_me v4.6.0beta1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4779,7 +4779,7 @@ fi
# Define the identity of the package.
PACKAGE='qbittorrent'
VERSION='v4.6.0alpha1'
VERSION='v4.6.0beta1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -7237,7 +7237,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by qbittorrent $as_me v4.6.0alpha1, which was
This file was extended by qbittorrent $as_me v4.6.0beta1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7297,7 +7297,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
qbittorrent config.status v4.6.0alpha1
qbittorrent config.status v4.6.0beta1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.6.0alpha1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.6.0beta1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""}

View File

@@ -98,8 +98,8 @@ Name[is]=qBittorrent
Comment[it]=Scarica e condividi file tramite BitTorrent
GenericName[it]=Client BitTorrent
Name[it]=qBittorrent
Comment[ja]=BitTorrent でファイルダウンロードおよび共有
GenericName[ja]=BitTorrent クライアント
Comment[ja]=BitTorrentでファイルダウンロード共有
GenericName[ja]=BitTorrentクライアント
Name[ja]=qBittorrent
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
GenericName[ka]=BitTorrent კლიენტი
@@ -178,7 +178,7 @@ Comment[zh_HK]=經由BitTorrent下載並分享檔案
GenericName[zh_HK]=BitTorrent用戶端
Name[zh_HK]=qBittorrent
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
GenericName[zh_TW]=BitTorrent 戶端
GenericName[zh_TW]=BitTorrent 戶端
Name[zh_TW]=qBittorrent
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
GenericName[eo]=BitTorrent-kliento
@@ -208,7 +208,7 @@ Name[ltg]=qBittorrent
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
GenericName[hi_IN]=Bittorrent साधन
Name[hi_IN]=qBittorrent
Comment[az@latin]=Faylları BitTorrent vasitəsilə ndərin və paylaşın
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
GenericName[az@latin]=BitTorrent client
Name[az@latin]=qBittorrent
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent

View File

@@ -32,6 +32,7 @@
#include <cstdio>
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QProcessEnvironment>
@@ -152,7 +153,7 @@ namespace
QStringList parts = arg.split(u'=');
if (parts.size() == 2)
return Utils::String::unquote(parts[1], u"'\""_qs);
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
.arg(fullParameter(), u"<value>"_qs));
}
@@ -203,7 +204,7 @@ namespace
const int res = val.toInt(&ok);
if (!ok)
{
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
.arg(fullParameter(), u"<integer value>"_qs));
}
@@ -219,7 +220,7 @@ namespace
int res = val.toInt(&ok);
if (!ok)
{
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
qDebug() << QCoreApplication::translate("CMD Options", "Expected integer number in environment variable '%1', but got '%2'")
.arg(envVarName(), val);
return defaultValue;
}
@@ -275,7 +276,7 @@ namespace
}
}
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--add-paused' must follow syntax "
"'--add-paused=<true|false>'")
.arg(fullParameter(), u"<true|false>"_qs));
@@ -302,7 +303,7 @@ namespace
return false;
}
qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'")
.arg(u"true|false"_qs, envVarName(), val);
return std::nullopt;
}
@@ -388,7 +389,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
{
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_qs));
}
else if (arg == TORRENTING_PORT_OPTION)
@@ -396,7 +397,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
result.torrentingPort = TORRENTING_PORT_OPTION.value(arg);
if ((result.torrentingPort < 1) || (result.torrentingPort > 65535))
{
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--torrenting-port"_qs));
}
}
@@ -499,58 +500,58 @@ QString makeUsage(const QString &prgName)
{
const QString indentation {USAGE_INDENTATION, u' '};
const QString text = QObject::tr("Usage:") + u'\n'
+ indentation + prgName + u' ' + QObject::tr("[options] [(<filename> | <url>)...]") + u'\n'
const QString text = QCoreApplication::translate("CMD Options", "Usage:") + u'\n'
+ indentation + prgName + u' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + u'\n'
+ QObject::tr("Options:") + u'\n'
+ QCoreApplication::translate("CMD Options", "Options:") + u'\n'
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
+ SHOW_VERSION_OPTION.usage() + wrapText(QObject::tr("Display program version and exit")) + u'\n'
+ SHOW_VERSION_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display program version and exit")) + u'\n'
#endif
+ SHOW_HELP_OPTION.usage() + wrapText(QObject::tr("Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the Web UI port"))
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port"))
+ u'\n'
+ TORRENTING_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the torrenting port"))
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
+ u'\n'
#ifndef DISABLE_GUI
+ NO_SPLASH_OPTION.usage() + wrapText(QObject::tr("Disable splash screen")) + u'\n'
+ NO_SPLASH_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Disable splash screen")) + u'\n'
#elif !defined(Q_OS_WIN)
+ DAEMON_OPTION.usage() + wrapText(QObject::tr("Run in daemon-mode (background)")) + u'\n'
+ DAEMON_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Run in daemon-mode (background)")) + u'\n'
#endif
//: Use appropriate short form or abbreviation of "directory"
+ PROFILE_OPTION.usage(QObject::tr("dir"))
+ wrapText(QObject::tr("Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ PROFILE_OPTION.usage(QCoreApplication::translate("CMD Options", "dir"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ RELATIVE_FASTRESUME.usage()
+ wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
+ wrapText(QCoreApplication::translate("CMD Options", "Hack into libtorrent fastresume files and make file paths relative "
"to the profile directory")) + u'\n'
+ Option::padUsageText(QObject::tr("files or URLs"))
+ wrapText(QObject::tr("Download the torrents passed by the user")) + u'\n'
+ Option::padUsageText(QCoreApplication::translate("CMD Options", "files or URLs"))
+ wrapText(QCoreApplication::translate("CMD Options", "Download the torrents passed by the user")) + u'\n'
+ u'\n'
+ wrapText(QObject::tr("Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QObject::tr("path")) + wrapText(QObject::tr("Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QObject::tr("Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QObject::tr("Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
+ wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QCoreApplication::translate("CMD Options", "path")) + wrapText(QCoreApplication::translate("CMD Options", "Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be "
"created.")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QObject::tr("Download files in sequential order")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Download files in sequential order")) + u'\n'
+ FIRST_AND_LAST_OPTION.usage()
+ wrapText(QObject::tr("Download first and last pieces first")) + u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Download first and last pieces first")) + u'\n'
+ SKIP_DIALOG_OPTION.usage()
+ wrapText(QObject::tr("Specify whether the \"Add New Torrent\" dialog opens when adding a "
+ wrapText(QCoreApplication::translate("CMD Options", "Specify whether the \"Add New Torrent\" dialog opens when adding a "
"torrent.")) + u'\n'
+ u'\n'
+ wrapText(QObject::tr("Option values may be supplied via environment variables. For option named "
+ wrapText(QCoreApplication::translate("CMD Options", "Option values may be supplied via environment variables. For option named "
"'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
"case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
"'TRUE'. For example, to disable the splash screen: "), 0) + u'\n'
+ u"QBT_NO_SPLASH=1 " + prgName + u'\n'
+ wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) + u'\n';
+ wrapText(QCoreApplication::translate("CMD Options", "Command line parameters take precedence over environment variables"), 0) + u'\n';
return text;
}
@@ -558,7 +559,7 @@ QString makeUsage(const QString &prgName)
void displayUsage(const QString &prgName)
{
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
QMessageBox msgBox(QMessageBox::Information, QCoreApplication::translate("CMD Options", "Help"), makeUsage(prgName), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

@@ -45,6 +45,7 @@
#include <io.h>
#endif
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
@@ -132,7 +133,7 @@ int main(int argc, char *argv[])
const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty())
{
throw CommandLineParameterError(QObject::tr("%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.")
.arg(params.unknownParameter));
}
@@ -144,7 +145,7 @@ int main(int argc, char *argv[])
displayVersion();
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-v (or --version)"_qs));
}
#endif
@@ -155,7 +156,7 @@ int main(int argc, char *argv[])
displayUsage(QString::fromLocal8Bit(argv[0]));
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-h (or --help)"_qs));
}
@@ -187,7 +188,7 @@ int main(int argc, char *argv[])
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize)
{
throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "You cannot use %1: qBittorrent is already running for this user.")
.arg(u"-d (or --daemon)"_qs));
}
#endif
@@ -295,15 +296,15 @@ void displayVersion()
void displayBadArgMessage(const QString &message)
{
const QString help = QObject::tr("Run application with -h option to read about command line parameters.");
const QString help = QCoreApplication::translate("Main", "Run application with -h option to read about command line parameters.");
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
QMessageBox msgBox(QMessageBox::Critical, QCoreApplication::translate("Main", "Bad command line"),
(message + u'\n' + help), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();
#else
const QString errMsg = QObject::tr("Bad command line: ") + u'\n'
const QString errMsg = QCoreApplication::translate("Main", "Bad command line: ") + u'\n'
+ message + u'\n'
+ help + u'\n';
fprintf(stderr, "%s", qUtf8Printable(errMsg));
@@ -316,10 +317,10 @@ bool userAgreesWithLegalNotice()
Q_ASSERT(!pref->getAcceptedLegal());
#ifdef DISABLE_GUI
const QString eula = u"\n*** %1 ***\n"_qs.arg(QObject::tr("Legal Notice"))
+ QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QObject::tr("No further notices will be issued.") + u"\n\n"
+ QObject::tr("Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
const QString eula = u"\n*** %1 ***\n"_qs.arg(QCoreApplication::translate("Main", "Legal Notice"))
+ QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QCoreApplication::translate("Main", "No further notices will be issued.") + u"\n\n"
+ QCoreApplication::translate("Main", "Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
printf("%s", qUtf8Printable(eula));
const char ret = getchar(); // Read pressed key
@@ -331,10 +332,10 @@ bool userAgreesWithLegalNotice()
}
#else
QMessageBox msgBox;
msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QObject::tr("Legal notice"));
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
msgBox.setText(QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QCoreApplication::translate("Main", "Legal notice"));
msgBox.addButton(QCoreApplication::translate("Main", "Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QCoreApplication::translate("Main", "I Agree"), QMessageBox::AcceptRole);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

@@ -29,6 +29,7 @@
#include "upgrade.h"
#include <QtGlobal>
#include <QCoreApplication>
#include <QMetaEnum>
#include "base/bittorrent/torrentcontentlayout.h"
@@ -54,7 +55,7 @@ namespace
SettingsStorage *settingsStorage {SettingsStorage::instance()};
const auto oldData {settingsStorage->loadValue<QByteArray>(oldKey)};
const auto newData {settingsStorage->loadValue<QString>(newKey)};
const QString errorMsgFormat {QObject::tr("Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
const QString errorMsgFormat {QCoreApplication::translate("Upgrade", "Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
if (!newData.isEmpty() || oldData.isEmpty())
return;
@@ -69,7 +70,7 @@ namespace
settingsStorage->storeValue(newKey, savePath);
settingsStorage->removeValue(oldKey);
LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
LogMsg(QCoreApplication::translate("Upgrade", "Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
, Log::INFO);
};
@@ -161,7 +162,7 @@ namespace
settingsStorage->storeValue(key, Scheduler::Days::Sunday);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -192,7 +193,7 @@ namespace
settingsStorage->storeValue(key, DNS::Service::NoIP);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -223,7 +224,7 @@ namespace
settingsStorage->storeValue(key, TrayIcon::Style::MonoLight);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -361,7 +362,7 @@ namespace
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;

View File

@@ -14,7 +14,7 @@
#define expected_lite_MAJOR 0
#define expected_lite_MINOR 6
#define expected_lite_PATCH 2
#define expected_lite_PATCH 3
#define expected_lite_VERSION expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH)
@@ -405,7 +405,7 @@ struct is_nothrow_swappable
};
} // namespace detail
// is [nothow] swappable:
// is [nothrow] swappable:
template< typename T >
struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
@@ -1002,11 +1002,12 @@ public:
// x.x.5.2.4 Swap
template< typename U=E >
nsel_REQUIRES_R( void,
std17::is_swappable<E>::value
std17::is_swappable<U>::value
)
swap( unexpected_type & other ) noexcept (
std17::is_nothrow_swappable<E>::value
std17::is_nothrow_swappable<U>::value
)
{
using std::swap;
@@ -2164,10 +2165,24 @@ private:
// x.x.4.6 expected<>: comparison operators
template< typename T1, typename E1, typename T2, typename E2 >
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
!std::is_void<T1>::value && !std::is_void<T2>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : *x == *y;
return bool(x) != bool(y) ? false : bool(x) ? *x == *y : x.error() == y.error();
}
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
std::is_void<T1>::value && std::is_void<T2>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) || static_cast<bool>( x.error() == y.error() );
}
template< typename T1, typename E1, typename T2, typename E2 >
@@ -2176,12 +2191,6 @@ constexpr bool operator!=( expected<T1,E1> const & x, expected<T2,E2> const & y
return !(x == y);
}
template< typename E1, typename E2 >
constexpr bool operator==( expected<void,E1> const & x, expected<void,E1> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : true;
}
#if nsel_P0323R <= 2
template< typename T, typename E >
@@ -2212,13 +2221,21 @@ constexpr bool operator>=( expected<T,E> const & x, expected<T,E> const & y )
// x.x.4.7 expected: comparison with T
template< typename T1, typename E1, typename T2 >
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, T2 const & v )
{
return bool(x) ? *x == v : false;
}
template< typename T1, typename E1, typename T2 >
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
constexpr bool operator==(T2 const & v, expected<T1,E1> const & x )
{
return bool(x) ? v == *x : false;

View File

@@ -36,6 +36,7 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QThread>
@@ -133,17 +134,19 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
const Path fastresumePath = path() / Path(idString + u".fastresume");
const Path torrentFilePath = path() / Path(idString + u".torrent");
QFile resumeDataFile {fastresumePath.data()};
if (!resumeDataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()));
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, MAX_TORRENT_SIZE);
if (!resumeDataReadResult)
return nonstd::make_unexpected(resumeDataReadResult.error().message);
QFile metadataFile {torrentFilePath.data()};
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()));
const QByteArray data = resumeDataFile.readAll();
const QByteArray metadata = (metadataFile.isOpen() ? metadataFile.readAll() : "");
const auto metadataReadResult = Utils::IO::readFile(torrentFilePath, MAX_TORRENT_SIZE);
if (!metadataReadResult)
{
if (metadataReadResult.error().status != Utils::IO::ReadError::NotExist)
return nonstd::make_unexpected(metadataReadResult.error().message);
}
const QByteArray data = resumeDataReadResult.value();
const QByteArray metadata = metadataReadResult.value_or(QByteArray());
return loadTorrentResumeData(data, metadata);
}
@@ -161,6 +164,8 @@ void BitTorrent::BencodeResumeDataStorage::doLoadAll() const
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
{
const int lineMaxLength = 48;
QFile queueFile {queueFilename.data()};
if (!queueFile.exists())
return;
@@ -175,7 +180,7 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
int start = 0;
while (true)
{
const auto line = QString::fromLatin1(queueFile.readLine().trimmed());
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
if (line.isEmpty())
break;

View File

@@ -41,7 +41,6 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QSet>
#include <QSqlDatabase>

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -33,6 +33,7 @@
#include "base/bittorrent/ltqbitarray.h"
#include "base/net/geoipmanager.h"
#include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "peeraddress.h"
using namespace BitTorrent;
@@ -168,6 +169,9 @@ bool PeerInfo::isPlaintextEncrypted() const
PeerAddress PeerInfo::address() const
{
if (useI2PSocket())
return {};
// fast path for platforms which boost.asio internal struct maps to `sockaddr`
return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()};
// slow path for the others
@@ -175,6 +179,23 @@ PeerAddress PeerInfo::address() const
// , m_nativeInfo.ip.port()};
}
QString PeerInfo::I2PAddress() const
{
if (!useI2PSocket())
return {};
#ifdef QBT_USES_LIBTORRENT2
if (m_I2PAddress.isEmpty())
{
const lt::sha256_hash destHash = m_nativeInfo.i2p_destination();
const QByteArray base32Dest = Utils::ByteArray::toBase32({destHash.data(), destHash.size()}).replace('=', "").toLower();
m_I2PAddress = QString::fromLatin1(base32Dest) + u".b32.i2p";
}
#endif
return m_I2PAddress;
}
QString PeerInfo::client() const
{
return QString::fromStdString(m_nativeInfo.client);

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -76,6 +76,7 @@ namespace BitTorrent
bool isPlaintextEncrypted() const;
PeerAddress address() const;
QString I2PAddress() const;
QString client() const;
QString peerIdClient() const;
qreal progress() const;
@@ -101,5 +102,6 @@ namespace BitTorrent
QString m_flagsDescription;
mutable QString m_country;
mutable QString m_I2PAddress;
};
}

View File

@@ -271,6 +271,14 @@ namespace BitTorrent
virtual void setI2PPort(int port) = 0;
virtual bool I2PMixedMode() const = 0;
virtual void setI2PMixedMode(bool enabled) = 0;
virtual int I2PInboundQuantity() const = 0;
virtual void setI2PInboundQuantity(int value) = 0;
virtual int I2POutboundQuantity() const = 0;
virtual void setI2POutboundQuantity(int value) = 0;
virtual int I2PInboundLength() const = 0;
virtual void setI2PInboundLength(int value) = 0;
virtual int I2POutboundLength() const = 0;
virtual void setI2POutboundLength(int value) = 0;
virtual bool isProxyPeerConnectionsEnabled() const = 0;
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
virtual ChokingAlgorithm chokingAlgorithm() const = 0;

View File

@@ -60,7 +60,6 @@
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
@@ -528,6 +527,10 @@ SessionImpl::SessionImpl(QObject *parent)
, m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_qs), u"127.0.0.1"_qs}
, m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_qs), 7656}
, m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_qs), false}
, m_I2PInboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/InboundQuantity"_qs), 3}
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_qs), 3}
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_qs), 3}
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_qs), 3}
, m_seedingLimitTimer {new QTimer(this)}
, m_resumeDataTimer {new QTimer(this)}
, m_ioThread {new QThread}
@@ -1678,6 +1681,14 @@ lt::settings_pack SessionImpl::loadLTSettings() const
settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
}
#ifdef QBT_USES_LIBTORRENT2
// I2P session options
settingsPack.set_int(lt::settings_pack::i2p_inbound_quantity, I2PInboundQuantity());
settingsPack.set_int(lt::settings_pack::i2p_outbound_quantity, I2POutboundQuantity());
settingsPack.set_int(lt::settings_pack::i2p_inbound_length, I2PInboundLength());
settingsPack.set_int(lt::settings_pack::i2p_outbound_length, I2POutboundLength());
#endif
// proxy
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
if (Preferences::instance()->useProxyForBT())
@@ -3640,6 +3651,62 @@ void SessionImpl::setI2PMixedMode(const bool enabled)
}
}
int SessionImpl::I2PInboundQuantity() const
{
return m_I2PInboundQuantity;
}
void SessionImpl::setI2PInboundQuantity(const int value)
{
if (value == m_I2PInboundQuantity)
return;
m_I2PInboundQuantity = value;
configureDeferred();
}
int SessionImpl::I2POutboundQuantity() const
{
return m_I2POutboundQuantity;
}
void SessionImpl::setI2POutboundQuantity(const int value)
{
if (value == m_I2POutboundQuantity)
return;
m_I2POutboundQuantity = value;
configureDeferred();
}
int SessionImpl::I2PInboundLength() const
{
return m_I2PInboundLength;
}
void SessionImpl::setI2PInboundLength(const int value)
{
if (value == m_I2PInboundLength)
return;
m_I2PInboundLength = value;
configureDeferred();
}
int SessionImpl::I2POutboundLength() const
{
return m_I2POutboundLength;
}
void SessionImpl::setI2POutboundLength(const int value)
{
if (value == m_I2POutboundLength)
return;
m_I2POutboundLength = value;
configureDeferred();
}
bool SessionImpl::isProxyPeerConnectionsEnabled() const
{
return m_isProxyPeerConnectionsEnabled;
@@ -5033,8 +5100,8 @@ void SessionImpl::loadCategories()
{
m_categories.clear();
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
if (!confFile.exists())
const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
if (!path.exists())
{
// TODO: Remove the following upgrade code in v4.5
// == BEGIN UPGRADE CODE ==
@@ -5045,26 +5112,27 @@ void SessionImpl::loadCategories()
// return;
}
if (!confFile.open(QFile::ReadOnly))
const int fileMaxSize = 1024 * 1024;
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Failed to load Categories. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Reason: invalid data format")
.arg(confFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
.arg(path.toString()), Log::WARNING);
return;
}

View File

@@ -250,6 +250,14 @@ namespace BitTorrent
void setI2PPort(int port) override;
bool I2PMixedMode() const override;
void setI2PMixedMode(bool enabled) override;
int I2PInboundQuantity() const override;
void setI2PInboundQuantity(int value) override;
int I2POutboundQuantity() const override;
void setI2POutboundQuantity(int value) override;
int I2PInboundLength() const override;
void setI2PInboundLength(int value) override;
int I2POutboundLength() const override;
void setI2POutboundLength(int value) override;
bool isProxyPeerConnectionsEnabled() const override;
void setProxyPeerConnectionsEnabled(bool enabled) override;
ChokingAlgorithm chokingAlgorithm() const override;
@@ -699,6 +707,10 @@ namespace BitTorrent
CachedSettingValue<QString> m_I2PAddress;
CachedSettingValue<int> m_I2PPort;
CachedSettingValue<bool> m_I2PMixedMode;
CachedSettingValue<int> m_I2PInboundQuantity;
CachedSettingValue<int> m_I2POutboundQuantity;
CachedSettingValue<int> m_I2PInboundLength;
CachedSettingValue<int> m_I2POutboundLength;
bool m_isRestored = false;

View File

@@ -46,7 +46,6 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QPointer>
#include <QSet>
#include <QStringList>

View File

@@ -103,28 +103,20 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
{
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(file.errorString());
if (file.size() > MAX_TORRENT_SIZE)
return nonstd::make_unexpected(tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE)));
QByteArray data;
try
{
data = file.readAll();
const auto readResult = Utils::IO::readFile(path, MAX_TORRENT_SIZE);
if (!readResult)
return nonstd::make_unexpected(readResult.error().message);
data = readResult.value();
}
catch (const std::bad_alloc &e)
{
return nonstd::make_unexpected(tr("Torrent file read error: %1").arg(QString::fromLocal8Bit(e.what())));
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), QString::fromLocal8Bit(e.what())));
}
if (data.size() != file.size())
return nonstd::make_unexpected(tr("Torrent file read error: size mismatch"));
file.close();
return load(data);
}

View File

@@ -47,6 +47,7 @@ namespace Http
inline const QString HEADER_CONTENT_LENGTH = u"content-length"_qs;
inline const QString HEADER_CONTENT_SECURITY_POLICY = u"content-security-policy"_qs;
inline const QString HEADER_CONTENT_TYPE = u"content-type"_qs;
inline const QString HEADER_CROSS_ORIGIN_OPENER_POLICY = u"cross-origin-opener-policy"_qs;
inline const QString HEADER_DATE = u"date"_qs;
inline const QString HEADER_HOST = u"host"_qs;
inline const QString HEADER_ORIGIN = u"origin"_qs;

View File

@@ -1224,6 +1224,16 @@ void Preferences::setConfirmRemoveAllTags(const bool enabled)
setValue(u"Preferences/Advanced/confirmRemoveAllTags"_qs, enabled);
}
bool Preferences::confirmPauseAndResumeAll() const
{
return value(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, true);
}
void Preferences::setConfirmPauseAndResumeAll(const bool enabled)
{
setValue(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, enabled);
}
#ifndef Q_OS_MACOS
TrayIcon::Style Preferences::trayIconStyle() const
{

View File

@@ -315,6 +315,8 @@ public:
void setConfirmTorrentRecheck(bool enabled);
bool confirmRemoveAllTags() const;
void setConfirmRemoveAllTags(bool enabled);
bool confirmPauseAndResumeAll() const;
void setConfirmPauseAndResumeAll(bool enabled);
#ifndef Q_OS_MACOS
bool systemTrayEnabled() const;
void setSystemTrayEnabled(bool enabled);

View File

@@ -31,7 +31,6 @@
#include "feed_serializer.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -46,23 +45,21 @@ const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QVector<QVariantHash>>();
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
{
QFile file {dataFileName.data()};
const int fileMaxSize = 10 * 1024 * 1024;
const auto readResult = Utils::IO::readFile(dataFileName, fileMaxSize);
if (!readResult)
{
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
emit loadingFinished({});
return;
}
if (!file.exists())
{
emit loadingFinished({});
}
else if (file.open(QFile::ReadOnly))
{
emit loadingFinished(loadArticles(file.readAll(), url));
file.close();
}
else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(dataFileName.toString(), file.errorString())
, Log::WARNING);
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
return;
}
emit loadingFinished(loadArticles(readResult.value(), url));
}
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)

View File

@@ -28,6 +28,8 @@
#include "rss_autodownloader.h"
#include <queue>
#include <QDataStream>
#include <QDebug>
#include <QJsonDocument>
@@ -46,6 +48,7 @@
#include "../logger.h"
#include "../profile.h"
#include "../utils/fs.h"
#include "../utils/io.h"
#include "rss_article.h"
#include "rss_autodownloadrule.h"
#include "rss_feed.h"
@@ -166,25 +169,27 @@ AutoDownloader *AutoDownloader::instance()
bool AutoDownloader::hasRule(const QString &ruleName) const
{
return m_rules.contains(ruleName);
return m_rulesByName.contains(ruleName);
}
AutoDownloadRule AutoDownloader::ruleByName(const QString &ruleName) const
{
return m_rules.value(ruleName, AutoDownloadRule(u"Unknown Rule"_qs));
const auto index = m_rulesByName.value(ruleName, -1);
return m_rules.value(index, AutoDownloadRule(u"Unknown Rule"_qs));
}
QList<AutoDownloadRule> AutoDownloader::rules() const
{
return m_rules.values();
return m_rules;
}
void AutoDownloader::insertRule(const AutoDownloadRule &rule)
void AutoDownloader::setRule(const AutoDownloadRule &rule)
{
if (!hasRule(rule.name()))
{
// Insert new rule
setRule_impl(rule);
sortRules();
m_dirty = true;
store();
emit ruleAdded(rule.name());
@@ -194,6 +199,7 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
{
// Update existing rule
setRule_impl(rule);
sortRules();
m_dirty = true;
storeDeferred();
emit ruleChanged(rule.name());
@@ -203,12 +209,12 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleName)
{
if (!hasRule(ruleName)) return false;
if (hasRule(newRuleName)) return false;
if (!hasRule(ruleName) || hasRule(newRuleName))
return false;
AutoDownloadRule rule = m_rules.take(ruleName);
rule.setName(newRuleName);
m_rules.insert(newRuleName, rule);
const auto index = m_rulesByName.take(ruleName);
m_rules[index].setName(newRuleName);
m_rulesByName.insert(newRuleName, index);
m_dirty = true;
store();
emit ruleRenamed(newRuleName, ruleName);
@@ -217,13 +223,21 @@ bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleN
void AutoDownloader::removeRule(const QString &ruleName)
{
if (m_rules.contains(ruleName))
if (!hasRule(ruleName))
return;
emit ruleAboutToBeRemoved(ruleName);
const auto index = m_rulesByName.take(ruleName);
m_rules.removeAt(index);
for (qsizetype i = index; i < m_rules.size(); ++i)
{
emit ruleAboutToBeRemoved(ruleName);
m_rules.remove(ruleName);
m_dirty = true;
store();
const AutoDownloadRule &rule = m_rules[i];
m_rulesByName[rule.name()] = i;
}
m_dirty = true;
store();
}
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
@@ -261,7 +275,7 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
{
for (const auto &rule : asConst(rulesFromJSON(data)))
insertRule(rule);
setRule(rule);
}
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
@@ -288,7 +302,7 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
throw ParsingError(tr("Invalid data format"));
for (const QVariant &val : asConst(dict))
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
setRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
}
QStringList AutoDownloader::smartEpisodeFilters() const
@@ -399,7 +413,31 @@ void AutoDownloader::handleFeedURLChanged(Feed *feed, const QString &oldURL)
void AutoDownloader::setRule_impl(const AutoDownloadRule &rule)
{
m_rules.insert(rule.name(), rule);
const QString ruleName = rule.name();
const auto index = m_rulesByName.value(ruleName, -1);
if (index < 0)
{
m_rules.append(rule);
m_rulesByName[ruleName] = m_rules.size() - 1;
}
else
{
m_rules[index] = rule;
}
}
void AutoDownloader::sortRules()
{
std::sort(m_rules.begin(), m_rules.end(), [](const AutoDownloadRule &lhs, const AutoDownloadRule &rhs)
{
return (lhs.priority() < rhs.priority());
});
for (qsizetype i = 0; i < m_rules.size(); ++i)
{
const AutoDownloadRule &rule = m_rules[i];
m_rulesByName[rule.name()] = i;
}
}
void AutoDownloader::addJobForArticle(const Article *article)
@@ -430,6 +468,9 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
m_dirty = true;
storeDeferred();
LogMsg(tr("RSS article '%1' is accepted by rule '%2'. Trying to add torrent...")
.arg(job->articleData.value(Article::KeyTitle).toString(), rule.name()));
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
BitTorrent::Session::instance()->addTorrent(torrentURL, rule.addTorrentParams());
@@ -453,21 +494,21 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
void AutoDownloader::load()
{
QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()};
const qint64 maxFileSize = 10 * 1024 * 1024;
const auto readResult = Utils::IO::readFile((m_fileStorage->storageDir() / Path(RULES_FILE_NAME)), maxFileSize);
if (!readResult)
{
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadRulesLegacy();
return;
}
if (!rulesFile.exists())
{
loadRulesLegacy();
}
else if (rulesFile.open(QFile::ReadOnly))
{
loadRules(rulesFile.readAll());
}
else
{
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
LogMsg((tr("Failed to read RSS AutoDownloader rules. %1").arg(readResult.error().message)), Log::WARNING);
return;
}
loadRules(readResult.value());
}
void AutoDownloader::loadRules(const QByteArray &data)
@@ -477,6 +518,7 @@ void AutoDownloader::loadRules(const QByteArray &data)
const auto rules = rulesFromJSON(data);
for (const auto &rule : rules)
setRule_impl(rule);
sortRules();
}
catch (const ParsingError &error)
{
@@ -493,7 +535,7 @@ void AutoDownloader::loadRulesLegacy()
{
const auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
if (!rule.name().isEmpty())
insertRule(rule);
setRule(rule);
}
}

View File

@@ -94,7 +94,7 @@ namespace RSS
AutoDownloadRule ruleByName(const QString &ruleName) const;
QList<AutoDownloadRule> rules() const;
void insertRule(const AutoDownloadRule &rule);
void setRule(const AutoDownloadRule &rule);
bool renameRule(const QString &ruleName, const QString &newRuleName);
void removeRule(const QString &ruleName);
@@ -118,6 +118,7 @@ namespace RSS
private:
void timerEvent(QTimerEvent *event) override;
void setRule_impl(const AutoDownloadRule &rule);
void sortRules();
void resetProcessingQueue();
void startProcessing();
void addJobForArticle(const Article *article);
@@ -141,7 +142,8 @@ namespace RSS
QTimer *m_processingTimer = nullptr;
Utils::Thread::UniquePtr m_ioThread;
AsyncFileStorage *m_fileStorage = nullptr;
QHash<QString, AutoDownloadRule> m_rules;
QList<AutoDownloadRule> m_rules;
QHash<QString, qsizetype> m_rulesByName;
QList<QSharedPointer<ProcessingJob>> m_processingQueue;
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
bool m_dirty = false;

View File

@@ -103,6 +103,7 @@ namespace
const QString S_NAME = u"name"_qs;
const QString S_ENABLED = u"enabled"_qs;
const QString S_PRIORITY = u"priority"_qs;
const QString S_USE_REGEX = u"useRegex"_qs;
const QString S_MUST_CONTAIN = u"mustContain"_qs;
const QString S_MUST_NOT_CONTAIN = u"mustNotContain"_qs;
@@ -126,6 +127,7 @@ namespace RSS
{
QString name;
bool enabled = true;
int priority = 0;
QStringList mustContain;
QStringList mustNotContain;
@@ -147,6 +149,7 @@ namespace RSS
{
return (left.name == right.name)
&& (left.enabled == right.enabled)
&& (left.priority == right.priority)
&& (left.mustContain == right.mustContain)
&& (left.mustNotContain == right.mustNotContain)
&& (left.episodeFilter == right.episodeFilter)
@@ -457,6 +460,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams;
return {{S_ENABLED, isEnabled()}
, {S_PRIORITY, priority()}
, {S_USE_REGEX, useRegex()}
, {S_MUST_CONTAIN, mustContain()}
, {S_MUST_NOT_CONTAIN, mustNotContain()}
@@ -483,11 +487,13 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
{
AutoDownloadRule rule {(name.isEmpty() ? jsonObj.value(S_NAME).toString() : name)};
rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true));
rule.setPriority(jsonObj.value(S_PRIORITY).toInt(0));
rule.setUseRegex(jsonObj.value(S_USE_REGEX).toBool(false));
rule.setMustContain(jsonObj.value(S_MUST_CONTAIN).toString());
rule.setMustNotContain(jsonObj.value(S_MUST_NOT_CONTAIN).toString());
rule.setEpisodeFilter(jsonObj.value(S_EPISODE_FILTER).toString());
rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true));
rule.setLastMatch(QDateTime::fromString(jsonObj.value(S_LAST_MATCH).toString(), Qt::RFC2822Date));
rule.setIgnoreDays(jsonObj.value(S_IGNORE_DAYS).toInt());
rule.setUseSmartFilter(jsonObj.value(S_SMART_FILTER).toBool(false));
@@ -665,6 +671,16 @@ void AutoDownloadRule::setEnabled(const bool enable)
m_dataPtr->enabled = enable;
}
int AutoDownloadRule::priority() const
{
return m_dataPtr->priority;
}
void AutoDownloadRule::setPriority(const int value)
{
m_dataPtr->priority = value;
}
QDateTime AutoDownloadRule::lastMatch() const
{
return m_dataPtr->lastMatch;

View File

@@ -61,6 +61,9 @@ namespace RSS
bool isEnabled() const;
void setEnabled(bool enable);
int priority() const;
void setPriority(int value);
QString mustContain() const;
void setMustContain(const QString &tokens);
QString mustNotContain() const;

View File

@@ -45,6 +45,7 @@
#include "../profile.h"
#include "../settingsstorage.h"
#include "../utils/fs.h"
#include "../utils/io.h"
#include "rss_article.h"
#include "rss_feed.h"
#include "rss_folder.h"
@@ -261,33 +262,35 @@ Item *Session::itemByPath(const QString &path) const
void Session::load()
{
QFile itemsFile {(m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME)).data()};
if (!itemsFile.exists())
{
loadLegacy();
return;
}
const int fileMaxSize = 10 * 1024 * 1024;
const Path path = m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME);
if (!itemsFile.open(QFile::ReadOnly))
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Couldn't read RSS session data. File: \"%1\". Error: \"%2\"")
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadLegacy();
return;
}
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse RSS session data. File: \"%1\". Error: \"%2\"")
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
LogMsg(tr("Failed to parse RSS session data. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Couldn't load RSS session data. File: \"%1\". Error: Invalid data format.")
.arg(itemsFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load RSS session data. File: \"%1\". Error: \"Invalid data format.\"")
.arg(path.toString()), Log::WARNING);
return;
}

View File

@@ -36,6 +36,7 @@
#include <QDomDocument>
#include <QDomElement>
#include <QDomNode>
#include <QFile>
#include <QPointer>
#include <QProcess>
#include <QUrl>
@@ -500,7 +501,6 @@ void SearchPluginManager::updateNova()
updateFile(Path(u"nova2.py"_qs), true);
updateFile(Path(u"nova2dl.py"_qs), true);
updateFile(Path(u"novaprinter.py"_qs), true);
updateFile(Path(u"sgmllib3.py"_qs), false);
updateFile(Path(u"socks.py"_qs), false);
}
@@ -518,7 +518,7 @@ void SearchPluginManager::update()
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
nova.waitForFinished();
const auto capabilities = QString::fromUtf8(nova.readAll());
const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput());
QDomDocument xmlDoc;
if (!xmlDoc.setContent(capabilities))
{
@@ -630,13 +630,15 @@ Path SearchPluginManager::pluginPath(const QString &name)
PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
{
const int lineMaxLength = 16;
QFile pluginFile {filePath.data()};
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
return {};
while (!pluginFile.atEnd())
{
const auto line = QString::fromUtf8(pluginFile.readLine()).remove(u' ');
const auto line = QString::fromUtf8(pluginFile.readLine(lineMaxLength)).remove(u' ');
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
const QString versionStr = line.mid(9);

View File

@@ -177,33 +177,35 @@ void TorrentFilesWatcher::initWorker()
void TorrentFilesWatcher::load()
{
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME)).data()};
if (!confFile.exists())
{
loadLegacy();
return;
}
const int fileMaxSize = 10 * 1024 * 1024;
const Path path = specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME);
if (!confFile.open(QFile::ReadOnly))
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Error: %2")
.arg(confFile.fileName(), confFile.errorString()), Log::WARNING);
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadLegacy();
return;
}
LogMsg(tr("Failed to load Watched Folders configuration. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse Watched Folders configuration from %1. Error: %2")
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
LogMsg(tr("Failed to parse Watched Folders configuration from %1. Error: \"%2\"")
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Invalid data format.")
.arg(confFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load Watched Folders configuration from %1. Error: \"Invalid data format.\"")
.arg(path.toString()), Log::WARNING);
return;
}
@@ -426,17 +428,26 @@ void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &wa
if (filePath.hasExtension(u".magnet"_qs))
{
const int fileMaxSize = 100 * 1024 * 1024;
QFile file {filePath.data()};
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
while (!file.atEnd())
if (file.size() <= fileMaxSize)
{
const auto line = QString::fromLatin1(file.readLine()).trimmed();
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
}
while (!file.atEnd())
{
const auto line = QString::fromLatin1(file.readLine()).trimmed();
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
}
file.close();
Utils::Fs::removeFile(filePath);
file.close();
Utils::Fs::removeFile(filePath);
}
else
{
LogMsg(tr("Magnet file too big. File: %1").arg(file.errorString()));
}
}
else
{

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1)
*
* This program is free software; you can redistribute it and/or
@@ -68,3 +69,45 @@ const QByteArray Utils::ByteArray::midView(const QByteArray &in, const int pos,
: len;
return QByteArray::fromRawData(in.constData() + pos, validLen);
}
QByteArray Utils::ByteArray::toBase32(const QByteArray &in)
{
const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const char padchar = '=';
const qsizetype inSize = in.size();
auto tmp = QByteArray((inSize + 4) / 5 * 8, Qt::Uninitialized);
qsizetype inIndex = 0;
char *out = tmp.data();
while (inIndex < inSize)
{
// encode 5 bytes at a time
qsizetype inPadLen = 5;
int64_t chunk = 0;
while (inPadLen > 0)
{
chunk |= static_cast<int64_t>(static_cast<uchar>(in.data()[inIndex++])) << (--inPadLen * 8);
if (inIndex == inSize)
break;
}
const int outCharCounts[] = {8, 7, 5, 4, 2};
for (int i = 7; i >= 0; --i)
{
if (i >= (8 - outCharCounts[inPadLen]))
{
const int shift = (i * 5);
const int64_t mask = static_cast<int64_t>(0x1f) << shift;
const int charIndex = (chunk & mask) >> shift;
*out++ = alphabet[charIndex];
}
else
{
*out++ = padchar;
}
}
}
return tmp;
}

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1)
*
* This program is free software; you can redistribute it and/or
@@ -41,4 +42,6 @@ namespace Utils::ByteArray
// Mimic QByteArray::mid(pos, len) but instead of returning a full-copy,
// we only return a partial view
const QByteArray midView(const QByteArray &in, int pos, int len = -1);
QByteArray toBase32(const QByteArray &in);
}

View File

@@ -31,7 +31,9 @@
#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include <QCoreApplication>
#include <QByteArray>
#include <QFile>
#include <QFileDevice>
#include <QSaveFile>
#include <QString>
@@ -69,6 +71,36 @@ Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operat
return *this;
}
nonstd::expected<QByteArray, Utils::IO::ReadError> Utils::IO::readFile(const Path &path, const qint64 maxSize, const QIODevice::OpenMode additionalMode)
{
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly | additionalMode))
{
const QString message = QCoreApplication::translate("Utils::IO", "File open error. File: \"%1\". Error: \"%2\"")
.arg(file.fileName(), file.errorString());
return nonstd::make_unexpected(ReadError {ReadError::NotExist, message});
}
const qint64 fileSize = file.size();
if ((maxSize >= 0) && (fileSize > maxSize))
{
const QString message = QCoreApplication::translate("Utils::IO", "File size exceeds limit. File: \"%1\". File size: %2. Size limit: %3")
.arg(file.fileName(), QString::number(fileSize), QString::number(maxSize));
return nonstd::make_unexpected(ReadError {ReadError::ExceedSize, message});
}
// Do not use `QIODevice::readAll()` it won't stop when reading `/dev/zero`
const QByteArray data = file.read(fileSize);
if (const qint64 dataSize = data.size(); dataSize != fileSize)
{
const QString message = QCoreApplication::translate("Utils::IO", "Read size mismatch. File: \"%1\". Expected: %2. Actual: %3")
.arg(file.fileName(), QString::number(fileSize), QString::number(dataSize));
return nonstd::make_unexpected(ReadError {ReadError::SizeMismatch, message});
}
return data;
}
nonstd::expected<void, QString> Utils::IO::saveToFile(const Path &path, const QByteArray &data)
{
if (const Path parentPath = path.parentPath(); !parentPath.isEmpty())

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