You've already forked qBittorrent
mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-10-09 18:32:15 +02:00
Compare commits
24 Commits
release-4.
...
release-4.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
118af03534 | ||
![]() |
596a71e03d | ||
![]() |
dab392645d | ||
![]() |
2d1fa9e154 | ||
![]() |
3a63fabe9c | ||
![]() |
3129712f03 | ||
![]() |
66baf64e17 | ||
![]() |
1bdeab398a | ||
![]() |
2bda2a37e3 | ||
![]() |
027b605fc0 | ||
![]() |
87e1c80e28 | ||
![]() |
2d3efbc711 | ||
![]() |
8e394e0cdb | ||
![]() |
0623c623d5 | ||
![]() |
307d8ec360 | ||
![]() |
5a518d2f35 | ||
![]() |
93fe20afcd | ||
![]() |
7f217110cd | ||
![]() |
4e7b33fadf | ||
![]() |
7926d1755f | ||
![]() |
3a13a3d5ca | ||
![]() |
caa8e1658a | ||
![]() |
890ccb7b84 | ||
![]() |
912b076707 |
20
Changelog
20
Changelog
@@ -1,3 +1,23 @@
|
||||
Wed Dec 18 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.1
|
||||
- FEATURE: Enable portable mode if "profile" directory exists (Tester798)
|
||||
- FEATURE: Enable "Apply rate limit to peers on LAN" option by default (Chocobo1)
|
||||
- BUGFIX: Sync translations from Transifex and run lupdate (sledgehammer999)
|
||||
- BUGFIX: Don't unnecessarily delete OS files in folders (sledgehammer999)
|
||||
- BUGFIX: Use the incomplete folder where appropriate (sledgehammer999)
|
||||
- BUGFIX: Align Properties tab bar correctly on window resize (Prince Gupta)
|
||||
- BUGFIX: Rework the listening IP/interface selection code (sledgehammer999)
|
||||
- BUGFIX: Fix inconsistent icon for deleting torrent (Chocobo1)
|
||||
- BUGFIX: Show torrent error message in transfer list (Chocobo1)
|
||||
- BUGFIX: Fix stuck in wrong torrent state (Chocobo1)
|
||||
- BUGFIX: Expand single-item folders in torrent content (warren)
|
||||
- WEBUI: Bump Web API version (sledgehammer999)
|
||||
- WEBUI: Add ability to rename torrent files from the WebUI (Thomas Piccirello)
|
||||
- WEBUI: Mention lack of HTTPS in WebUI magnet link warning (nl6720)
|
||||
- WEBUI: Fix HTML elements size in search tab (Chocobo1)
|
||||
- SEARCH: Fix incorrect translation displayed after language change (Chocobo1)
|
||||
- SEARCH: Fix missing translations in search plugins dialog (Chocobo1)
|
||||
- WINDOWS: Update russian translation of the installer (Andrei Stepanov)
|
||||
|
||||
Tue Dec 03 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.2.0
|
||||
- FEATURE: Libtorrent 1.2.x series are supported now (glassez)
|
||||
- FEATURE: Add OpenSSL version to GUI and stackdump (Chocobo1)
|
||||
|
@@ -74,18 +74,17 @@ macro(qbt_set_compiler_options)
|
||||
string(APPEND CMAKE_CXX_FLAGS " ${_GCC_COMMON_C_AND_CXX_FLAGS_STRING} ${_GCC_COMMON_CXX_FLAGS_STRING}")
|
||||
|
||||
# check whether we can enable -Og optimization for debug build
|
||||
# also let's enable -march=native for debug builds
|
||||
check_cxx_compiler_flag(-Og _DEBUG_OPTIMIZATION_LEVEL_IS_SUPPORTED)
|
||||
|
||||
if (_DEBUG_OPTIMIZATION_LEVEL_IS_SUPPORTED)
|
||||
set(QBT_ADDITONAL_FLAGS "-Og -g3 -march=native -pipe" CACHE STRING
|
||||
set(QBT_ADDITONAL_FLAGS "-Og -g3 -pipe" CACHE STRING
|
||||
"Additional qBittorent compile flags")
|
||||
set(QBT_ADDITONAL_CXX_FLAGS "-Og -g3 -march=native -pipe" CACHE STRING
|
||||
set(QBT_ADDITONAL_CXX_FLAGS "-Og -g3 -pipe" CACHE STRING
|
||||
"Additional qBittorent C++ compile flags")
|
||||
else(_DEBUG_OPTIMIZATION_LEVEL_IS_SUPPORTED)
|
||||
set(QBT_ADDITONAL_FLAGS "-O0 -g3 -march=native -pipe" CACHE STRING
|
||||
set(QBT_ADDITONAL_FLAGS "-O0 -g3 -pipe" CACHE STRING
|
||||
"Additional qBittorent compile flags")
|
||||
set(QBT_ADDITONAL_CXX_FLAGS "-O0 -g3 -march=native -pipe" CACHE STRING
|
||||
set(QBT_ADDITONAL_CXX_FLAGS "-O0 -g3 -pipe" CACHE STRING
|
||||
"Additional qBittorent C++ compile flags")
|
||||
endif (_DEBUG_OPTIMIZATION_LEVEL_IS_SUPPORTED)
|
||||
endif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
|
24
configure
vendored
24
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.2.0alpha.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.2.1.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -580,8 +580,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.2.0alpha'
|
||||
PACKAGE_STRING='qbittorrent v4.2.0alpha'
|
||||
PACKAGE_VERSION='v4.2.1'
|
||||
PACKAGE_STRING='qbittorrent v4.2.1'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -1302,7 +1302,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.2.0alpha to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.2.1 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1373,7 +1373,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.2.0alpha:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.2.1:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1509,7 +1509,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.2.0alpha
|
||||
qbittorrent configure v4.2.1
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 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.2.0alpha, which was
|
||||
It was created by qbittorrent $as_me v4.2.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3826,7 +3826,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.2.0alpha'
|
||||
VERSION='v4.2.1'
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
@@ -6343,7 +6343,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.2.0alpha, which was
|
||||
This file was extended by qbittorrent $as_me v4.2.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -6401,7 +6401,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.2.0alpha
|
||||
qbittorrent config.status v4.2.1
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -7659,7 +7659,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.2.0alpha, which was
|
||||
This file was extended by qbittorrent $as_me v4.2.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7717,7 +7717,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.2.0alpha
|
||||
qbittorrent config.status v4.2.1
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.2.0alpha], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.2.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_PROG_CC
|
||||
|
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.2.0</string>
|
||||
<string>4.2.1</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
16
dist/windows/installer-translations/russian.nsi
vendored
16
dist/windows/installer-translations/russian.nsi
vendored
@@ -9,23 +9,23 @@ LangString inst_startmenu ${LANG_RUSSIAN} "Создать ярлык в меню
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_RUSSIAN} "Открывать торрент-файлы с помощью qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_RUSSIAN} "Открывать magnet-ссылки с помощью qBittorrent"
|
||||
LangString inst_magnet ${LANG_RUSSIAN} "Открывать магнет-ссылки с помощью qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_RUSSIAN} "Добавить в список исключений брандмауера"
|
||||
LangString inst_firewall ${LANG_RUSSIAN} "Добавить в список исключений брандмауэра"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_RUSSIAN} "Добавление в список исключений брандмауера"
|
||||
LangString inst_firewallinfo ${LANG_RUSSIAN} "Добавление в список исключений брандмауэра"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_RUSSIAN} "qBittorrent запущен. Пожалуйста, закройте qBittorrent и перезапустите программу установки."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "A previous installation was detected. It will be uninstalled without deleting user settings."
|
||||
LangString inst_uninstall_question ${LANG_RUSSIAN} "Обнаружена предыдущая установка. Она будет деинсталлирована без удаления пользовательских настроек."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_RUSSIAN} "Деинсталлируем старую версию."
|
||||
LangString inst_unist ${LANG_RUSSIAN} "Деинсталлируется старая версия."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_RUSSIAN} "Запустить qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_RUSSIAN} "Этот установщик работает только на 64-битных версиях Windows."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_RUSSIAN} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_RUSSIAN} "Для работы этой версии qBittorrent требуется Windows 7 или выше."
|
||||
|
||||
|
||||
;------------------------------------
|
||||
@@ -42,9 +42,9 @@ LangString remove_registry ${LANG_RUSSIAN} "Удалить данные из р
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_RUSSIAN} "Удалить пользовательские настройки"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_RUSSIAN} "Удалить из списка исключений брандмауера"
|
||||
LangString remove_firewall ${LANG_RUSSIAN} "Удалить из списка исключений брандмауэра"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_RUSSIAN} "Удаление из списка исключений брандмауера"
|
||||
LangString remove_firewallinfo ${LANG_RUSSIAN} "Удаление из списка исключений брандмауэра"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_RUSSIAN} "Удалить сохранённые торрент-файлы"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
@@ -52,4 +52,4 @@ LangString uninst_warning ${LANG_RUSSIAN} "qBittorrent запущен. Пожа
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_RUSSIAN} "Ассоциации торрент-файлов не сброшены. Уже ассоциированы с:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_RUSSIAN} "Ассоциации magnet-ссылок не сброшены. Уже ассоциированы с:"
|
||||
LangString uninst_mag_warn ${LANG_RUSSIAN} "Ассоциации магнет-ссылок не сброшены. Уже ассоциированы с:"
|
||||
|
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -28,7 +28,7 @@ XPStyle on
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
; Program specific
|
||||
!define PROG_VERSION "4.2.0"
|
||||
!define PROG_VERSION "4.2.1"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
|
@@ -144,13 +144,13 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE);
|
||||
#endif
|
||||
|
||||
validateCommandLineParameters();
|
||||
const bool portableModeEnabled = m_commandLineArgs.profileDir.isEmpty() && QDir(QCoreApplication::applicationDirPath()).exists(DEFAULT_PORTABLE_MODE_PROFILE_DIR);
|
||||
|
||||
const QString profileDir = m_commandLineArgs.portableMode
|
||||
const QString profileDir = portableModeEnabled
|
||||
? QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(DEFAULT_PORTABLE_MODE_PROFILE_DIR)
|
||||
: m_commandLineArgs.profileDir;
|
||||
Profile::initialize(profileDir, m_commandLineArgs.configurationName,
|
||||
m_commandLineArgs.relativeFastresumePaths || m_commandLineArgs.portableMode);
|
||||
(m_commandLineArgs.relativeFastresumePaths || portableModeEnabled));
|
||||
|
||||
Logger::initInstance();
|
||||
SettingsStorage::initInstance();
|
||||
@@ -171,6 +171,14 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
|
||||
Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION));
|
||||
if (portableModeEnabled) {
|
||||
Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir));
|
||||
if (m_commandLineArgs.relativeFastresumePaths)
|
||||
Logger::instance()->addMessage(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg("--relative-fastresume"), Log::WARNING); // to avoid translating the `--relative-fastresume` string
|
||||
}
|
||||
else {
|
||||
Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance().location(SpecialFolder::Config)));
|
||||
}
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
@@ -737,12 +745,3 @@ void Application::cleanup()
|
||||
Utils::Misc::shutdownComputer(m_shutdownAct);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::validateCommandLineParameters()
|
||||
{
|
||||
if (m_commandLineArgs.portableMode && !m_commandLineArgs.profileDir.isEmpty())
|
||||
throw CommandLineParameterError(tr("Portable mode and explicit profile directory options are mutually exclusive"));
|
||||
|
||||
if (m_commandLineArgs.portableMode && m_commandLineArgs.relativeFastresumePaths)
|
||||
Logger::instance()->addMessage(tr("Portable mode implies relative fastresume"), Log::WARNING);
|
||||
}
|
||||
|
@@ -144,5 +144,4 @@ private:
|
||||
void processParams(const QStringList ¶ms);
|
||||
void runExternalProgram(const BitTorrent::TorrentHandle *torrent) const;
|
||||
void sendNotificationEmail(const BitTorrent::TorrentHandle *torrent);
|
||||
void validateCommandLineParameters();
|
||||
};
|
||||
|
@@ -318,7 +318,6 @@ namespace
|
||||
constexpr const IntOption WEBUI_PORT_OPTION {"webui-port"};
|
||||
constexpr const StringOption PROFILE_OPTION {"profile"};
|
||||
constexpr const StringOption CONFIGURATION_OPTION {"configuration"};
|
||||
constexpr const BoolOption PORTABLE_OPTION {"portable"};
|
||||
constexpr const BoolOption RELATIVE_FASTRESUME {"relative-fastresume"};
|
||||
constexpr const StringOption SAVE_PATH_OPTION {"save-path"};
|
||||
constexpr const TriStateBoolOption PAUSED_OPTION {"add-paused", true};
|
||||
@@ -332,7 +331,6 @@ namespace
|
||||
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
|
||||
: showHelp(false)
|
||||
, relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
|
||||
, portableMode(PORTABLE_OPTION.value(env))
|
||||
, skipChecking(SKIP_HASH_CHECK_OPTION.value(env))
|
||||
, sequential(SEQUENTIAL_OPTION.value(env))
|
||||
, firstLastPiecePriority(FIRST_AND_LAST_OPTION.value(env))
|
||||
@@ -437,9 +435,6 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
else if (arg == RELATIVE_FASTRESUME) {
|
||||
result.relativeFastresumePaths = true;
|
||||
}
|
||||
else if (arg == PORTABLE_OPTION) {
|
||||
result.portableMode = true;
|
||||
}
|
||||
else if (arg == CONFIGURATION_OPTION) {
|
||||
result.configurationName = CONFIGURATION_OPTION.value(arg);
|
||||
}
|
||||
@@ -544,9 +539,6 @@ QString makeUsage(const QString &prgName)
|
||||
stream << RELATIVE_FASTRESUME.usage()
|
||||
<< wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
|
||||
"to the profile directory")) << '\n';
|
||||
stream << PORTABLE_OPTION.usage()
|
||||
<< wrapText(QObject::tr("Shortcut for %1", "Shortcut for --profile=<exe dir>/profile --relative-fastresume")
|
||||
.arg(QLatin1String("--profile=<exe dir>/profile --relative-fastresume"))) << '\n';
|
||||
stream << Option::padUsageText(QObject::tr("files or URLs"))
|
||||
<< wrapText(QObject::tr("Download the torrents passed by the user")) << '\n'
|
||||
<< '\n';
|
||||
|
@@ -42,7 +42,7 @@ class QProcessEnvironment;
|
||||
|
||||
struct QBtCommandLineParameters
|
||||
{
|
||||
bool showHelp, relativeFastresumePaths, portableMode, skipChecking, sequential, firstLastPiecePriority;
|
||||
bool showHelp, relativeFastresumePaths, skipChecking, sequential, firstLastPiecePriority;
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
bool showVersion;
|
||||
#endif
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -303,8 +303,6 @@ namespace BitTorrent
|
||||
void setNetworkInterfaceName(const QString &name);
|
||||
QString networkInterfaceAddress() const;
|
||||
void setNetworkInterfaceAddress(const QString &address);
|
||||
bool isIPv6Enabled() const;
|
||||
void setIPv6Enabled(bool enabled);
|
||||
int encryption() const;
|
||||
void setEncryption(int state);
|
||||
bool isProxyPeerConnectionsEnabled() const;
|
||||
@@ -532,6 +530,7 @@ namespace BitTorrent
|
||||
void configureComponents();
|
||||
void initializeNativeSession();
|
||||
void loadLTSettings(lt::settings_pack &settingsPack);
|
||||
void configureNetworkInterfaces(lt::settings_pack &settingsPack);
|
||||
void configurePeerClasses();
|
||||
void adjustLimits(lt::settings_pack &settingsPack);
|
||||
void applyBandwidthLimits(lt::settings_pack &settingsPack) const;
|
||||
@@ -662,7 +661,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<QString> m_networkInterface;
|
||||
CachedSettingValue<QString> m_networkInterfaceName;
|
||||
CachedSettingValue<QString> m_networkInterfaceAddress;
|
||||
CachedSettingValue<bool> m_isIPv6Enabled;
|
||||
CachedSettingValue<int> m_encryption;
|
||||
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
|
||||
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
|
||||
|
@@ -899,7 +899,10 @@ TorrentState TorrentHandle::state() const
|
||||
|
||||
void TorrentHandle::updateState()
|
||||
{
|
||||
if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) {
|
||||
if (hasError()) {
|
||||
m_state = TorrentState::Error;
|
||||
}
|
||||
else if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) {
|
||||
m_state = TorrentState::CheckingResumeData;
|
||||
}
|
||||
else if (isMoveInProgress()) {
|
||||
@@ -908,8 +911,6 @@ void TorrentHandle::updateState()
|
||||
else if (isPaused()) {
|
||||
if (hasMissingFiles())
|
||||
m_state = TorrentState::MissingFiles;
|
||||
else if (hasError())
|
||||
m_state = TorrentState::Error;
|
||||
else
|
||||
m_state = isSeed() ? TorrentState::PausedUploading : TorrentState::PausedDownloading;
|
||||
}
|
||||
@@ -961,12 +962,7 @@ bool TorrentHandle::hasMissingFiles() const
|
||||
|
||||
bool TorrentHandle::hasError() const
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM < 10200)
|
||||
return (m_nativeStatus.paused && m_nativeStatus.errc);
|
||||
#else
|
||||
return ((m_nativeStatus.flags & lt::torrent_flags::paused)
|
||||
&& m_nativeStatus.errc);
|
||||
#endif
|
||||
return static_cast<bool>(m_nativeStatus.errc);
|
||||
}
|
||||
|
||||
bool TorrentHandle::hasFilteredPieces() const
|
||||
|
@@ -318,7 +318,7 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
|
||||
|
||||
QString SearchPluginManager::categoryFullName(const QString &categoryName)
|
||||
{
|
||||
static const QHash<QString, QString> categoryTable {
|
||||
const QHash<QString, QString> categoryTable {
|
||||
{"all", tr("All categories")},
|
||||
{"movies", tr("Movies")},
|
||||
{"tv", tr("TV shows")},
|
||||
|
@@ -86,7 +86,6 @@ namespace
|
||||
{"BitTorrent/Session/BandwidthSchedulerEnabled", "Preferences/Scheduler/Enabled"},
|
||||
{"BitTorrent/Session/Port", "Preferences/Connection/PortRangeMin"},
|
||||
{"BitTorrent/Session/UseRandomPort", "Preferences/General/UseRandomPort"},
|
||||
{"BitTorrent/Session/IPv6Enabled", "Preferences/Connection/InterfaceListenIPv6"},
|
||||
{"BitTorrent/Session/Interface", "Preferences/Connection/Interface"},
|
||||
{"BitTorrent/Session/InterfaceName", "Preferences/Connection/InterfaceName"},
|
||||
{"BitTorrent/Session/InterfaceAddress", "Preferences/Connection/InterfaceAddress"},
|
||||
|
@@ -99,9 +99,13 @@ QString Utils::Fs::folderName(const QString &filePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will first remove system cache files, e.g. `Thumbs.db`,
|
||||
* `.DS_Store`. Then will try to remove the whole tree if the tree consist
|
||||
* only of folders
|
||||
* This function will first check if there are only system cache files, e.g. `Thumbs.db`,
|
||||
* `.DS_Store` and/or only temp files that end with '~', e.g. `filename~`.
|
||||
* If they are the only files it will try to remove them and delete the folder.
|
||||
* This action will be performed for each subfolder starting from the deepest folder.
|
||||
* There is an inherent race condition here. A file might appear after it is checked
|
||||
* that only the above mentioned "useless" files exist but before the whole folder is removed.
|
||||
* In this case, the folder will not be removed but the "useless" files will be deleted.
|
||||
*/
|
||||
bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
||||
{
|
||||
@@ -110,12 +114,12 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
||||
|
||||
const QStringList deleteFilesList = {
|
||||
// Windows
|
||||
"Thumbs.db",
|
||||
"desktop.ini",
|
||||
QLatin1String("Thumbs.db"),
|
||||
QLatin1String("desktop.ini"),
|
||||
// Linux
|
||||
".directory",
|
||||
QLatin1String(".directory"),
|
||||
// Mac OS
|
||||
".DS_Store"
|
||||
QLatin1String(".DS_Store")
|
||||
};
|
||||
|
||||
// travel from the deepest folder and remove anything unwanted on the way out.
|
||||
@@ -128,18 +132,25 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
||||
, [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); });
|
||||
|
||||
for (const QString &p : asConst(dirList)) {
|
||||
// remove unwanted files
|
||||
for (const QString &f : deleteFilesList) {
|
||||
forceRemove(p + f);
|
||||
}
|
||||
|
||||
// remove temp files on linux (file ends with '~'), e.g. `filename~`
|
||||
const QDir dir(p);
|
||||
// A deeper folder may have not been removed in the previous iteration
|
||||
// so don't remove anything from this folder either.
|
||||
if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
continue;
|
||||
|
||||
const QStringList tmpFileList = dir.entryList(QDir::Files);
|
||||
for (const QString &f : tmpFileList) {
|
||||
if (f.endsWith('~'))
|
||||
forceRemove(p + f);
|
||||
}
|
||||
|
||||
// deleteFilesList contains unwanted files, usually created by the OS
|
||||
// temp files on linux usually end with '~', e.g. `filename~`
|
||||
const bool hasOtherFiles = std::any_of(tmpFileList.cbegin(), tmpFileList.cend(), [&deleteFilesList](const QString &f)
|
||||
{
|
||||
return (!f.endsWith('~') && !deleteFilesList.contains(f, Qt::CaseInsensitive));
|
||||
});
|
||||
if (hasOtherFiles)
|
||||
continue;
|
||||
|
||||
for (const QString &f : tmpFileList)
|
||||
forceRemove(p + f);
|
||||
|
||||
// remove directory if empty
|
||||
dir.rmdir(p);
|
||||
|
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "net.h"
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QString>
|
||||
@@ -91,6 +92,37 @@ namespace Utils
|
||||
return subnet.first.toString() + '/' + QString::number(subnet.second);
|
||||
}
|
||||
|
||||
QHostAddress canonicalIPv6Addr(const QHostAddress &addr)
|
||||
{
|
||||
// Link-local IPv6 textual address always contains a scope id (or zone index)
|
||||
// The scope id is appended to the IPv6 address using the '%' character
|
||||
// The scope id can be either a interface name or an interface number
|
||||
// Examples:
|
||||
// fe80::1%ethernet_17
|
||||
// fe80::1%13
|
||||
// The interface number is the mandatory supported way
|
||||
// Unfortunately for us QHostAddress::toString() outputs (at least on Windows)
|
||||
// the interface name, and libtorrent/boost.asio only support an interface number
|
||||
// as scope id. Furthermore, QHostAddress doesn't have any convenient method to
|
||||
// affect this, so we jump through hoops here.
|
||||
if (addr.protocol() != QAbstractSocket::IPv6Protocol)
|
||||
return QHostAddress{addr.toIPv6Address()};
|
||||
|
||||
// QHostAddress::setScopeId(addr.scopeId()); // Even though the docs say that setScopeId
|
||||
// will convert a name to a number, this doesn't happen. Probably a Qt bug.
|
||||
const QString scopeIdTxt = addr.scopeId();
|
||||
if (scopeIdTxt.isEmpty())
|
||||
return addr;
|
||||
|
||||
const int id = QNetworkInterface::interfaceIndexFromName(scopeIdTxt);
|
||||
if (id == 0) // This failure might mean that the scope id was already a number
|
||||
return addr;
|
||||
|
||||
QHostAddress canonical(addr.toIPv6Address());
|
||||
canonical.setScopeId(QString::number(id));
|
||||
return canonical;
|
||||
}
|
||||
|
||||
QList<QSslCertificate> loadSSLCertificate(const QByteArray &data)
|
||||
{
|
||||
const QList<QSslCertificate> certs {QSslCertificate::fromData(data)};
|
||||
|
@@ -50,6 +50,7 @@ namespace Utils
|
||||
bool isLoopbackAddress(const QHostAddress &addr);
|
||||
bool isIPInRange(const QHostAddress &addr, const QVector<Subnet> &subnets);
|
||||
QString subnetToString(const Subnet &subnet);
|
||||
QHostAddress canonicalIPv6Addr(const QHostAddress &addr);
|
||||
|
||||
const int MAX_SSL_FILE_SIZE = 1024 * 1024;
|
||||
QList<QSslCertificate> loadSSLCertificate(const QByteArray &data);
|
||||
|
@@ -625,8 +625,12 @@ void AddNewTorrentDialog::setupTreeview()
|
||||
m_ui->contentTreeView->hideColumn(REMAINING);
|
||||
m_ui->contentTreeView->hideColumn(AVAILABILITY);
|
||||
|
||||
// Expand root folder
|
||||
m_ui->contentTreeView->setExpanded(m_contentModel->index(0, 0), true);
|
||||
// Expand single-item folders recursively
|
||||
QModelIndex currentIndex;
|
||||
while (m_contentModel->rowCount(currentIndex) == 1) {
|
||||
currentIndex = m_contentModel->index(0, 0, currentIndex);
|
||||
m_ui->contentTreeView->setExpanded(currentIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
updateDiskSpaceLabel();
|
||||
|
@@ -68,7 +68,6 @@ enum AdvSettingsRows
|
||||
NETWORK_IFACE,
|
||||
//Optional network address
|
||||
NETWORK_IFACE_ADDRESS,
|
||||
NETWORK_LISTEN_IPV6,
|
||||
// behavior
|
||||
SAVE_RESUME_DATA_INTERVAL,
|
||||
CONFIRM_RECHECK_TORRENT,
|
||||
@@ -227,18 +226,14 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
}
|
||||
|
||||
// Interface address
|
||||
if (m_comboBoxInterfaceAddress.currentIndex() == 0) {
|
||||
// All addresses (default)
|
||||
session->setNetworkInterfaceAddress({});
|
||||
}
|
||||
else {
|
||||
QHostAddress ifaceAddr(m_comboBoxInterfaceAddress.currentText().trimmed());
|
||||
ifaceAddr.isNull() ? session->setNetworkInterfaceAddress({}) : session->setNetworkInterfaceAddress(ifaceAddr.toString());
|
||||
}
|
||||
session->setIPv6Enabled(m_checkBoxListenIPv6.isChecked());
|
||||
// Construct a QHostAddress to filter malformed strings
|
||||
const QHostAddress ifaceAddr(m_comboBoxInterfaceAddress.currentData().toString().trimmed());
|
||||
session->setNetworkInterfaceAddress(ifaceAddr.toString());
|
||||
|
||||
// Announce IP
|
||||
QHostAddress addr(m_lineEditAnnounceIP.text().trimmed());
|
||||
session->setAnnounceIP(addr.isNull() ? "" : addr.toString());
|
||||
// Construct a QHostAddress to filter malformed strings
|
||||
const QHostAddress addr(m_lineEditAnnounceIP.text().trimmed());
|
||||
session->setAnnounceIP(addr.toString());
|
||||
|
||||
// Program notification
|
||||
MainWindow *const mainWindow = static_cast<Application*>(QCoreApplication::instance())->mainWindow();
|
||||
@@ -295,33 +290,35 @@ void AdvancedSettings::updateInterfaceAddressCombo()
|
||||
|
||||
// Clear all items and reinsert them, default to all
|
||||
m_comboBoxInterfaceAddress.clear();
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All addresses"));
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(0);
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All addresses"), {});
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv4 addresses"), QLatin1String("0.0.0.0"));
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv6 addresses"), QLatin1String("::"));
|
||||
|
||||
auto populateCombo = [this, ¤tAddress](const QString &ip, const QAbstractSocket::NetworkLayerProtocol &protocol)
|
||||
const auto populateCombo = [this, ¤tAddress](const QHostAddress &addr)
|
||||
{
|
||||
Q_ASSERT((protocol == QAbstractSocket::IPv4Protocol) || (protocol == QAbstractSocket::IPv6Protocol));
|
||||
// Only take ipv4 for now?
|
||||
if ((protocol != QAbstractSocket::IPv4Protocol) && (protocol != QAbstractSocket::IPv6Protocol))
|
||||
return;
|
||||
m_comboBoxInterfaceAddress.addItem(ip);
|
||||
//Try to select the last added one
|
||||
if (ip == currentAddress)
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(m_comboBoxInterfaceAddress.count() - 1);
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
const QString str = addr.toString();
|
||||
m_comboBoxInterfaceAddress.addItem(str, str);
|
||||
}
|
||||
else if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
const QString str = Utils::Net::canonicalIPv6Addr(addr).toString();
|
||||
m_comboBoxInterfaceAddress.addItem(str, str);
|
||||
}
|
||||
};
|
||||
|
||||
if (ifaceName.isEmpty()) {
|
||||
for (const QHostAddress &ip : asConst(QNetworkInterface::allAddresses()))
|
||||
populateCombo(ip.toString(), ip.protocol());
|
||||
for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
|
||||
populateCombo(addr);
|
||||
}
|
||||
else {
|
||||
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
|
||||
const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : addresses) {
|
||||
const QHostAddress ip = entry.ip();
|
||||
populateCombo(ip.toString(), ip.protocol());
|
||||
}
|
||||
for (const QNetworkAddressEntry &entry : addresses)
|
||||
populateCombo(entry.ip());
|
||||
}
|
||||
|
||||
const int index = m_comboBoxInterfaceAddress.findData(currentAddress);
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(std::max(index, 0));
|
||||
}
|
||||
|
||||
void AdvancedSettings::loadAdvancedSettings()
|
||||
@@ -524,9 +521,6 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
// Network interface address
|
||||
updateInterfaceAddressCombo();
|
||||
addRow(NETWORK_IFACE_ADDRESS, tr("Optional IP Address to bind to (requires restart)"), &m_comboBoxInterfaceAddress);
|
||||
// Listen on IPv6 address
|
||||
m_checkBoxListenIPv6.setChecked(session->isIPv6Enabled());
|
||||
addRow(NETWORK_LISTEN_IPV6, tr("Listen on IPv6 address (requires restart)"), &m_checkBoxListenIPv6);
|
||||
// Announce IP
|
||||
m_lineEditAnnounceIP.setText(session->announceIP());
|
||||
addRow(ANNOUNCE_IP, tr("IP Address to report to trackers (requires restart)"), &m_lineEditAnnounceIP);
|
||||
|
@@ -63,7 +63,7 @@ private:
|
||||
m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxSavePathHistoryLength;
|
||||
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts, m_checkBoxSuperSeeding,
|
||||
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
||||
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxListenIPv6, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
||||
m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxSuggestMode, m_checkBoxCoalesceRW, m_checkBoxSpeedWidgetEnabled;
|
||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm, m_comboBoxSeedChokingAlgorithm;
|
||||
QLineEdit m_lineEditAnnounceIP;
|
||||
|
@@ -194,13 +194,15 @@ void PropertiesWidget::showPiecesDownloaded(bool show)
|
||||
void PropertiesWidget::setVisibility(bool visible)
|
||||
{
|
||||
if (!visible && (m_state == VISIBLE)) {
|
||||
const int tabBarHeight = m_tabBar->geometry().height(); // take height before hiding
|
||||
auto *hSplitter = static_cast<QSplitter *>(parentWidget());
|
||||
m_ui->stackedProperties->setVisible(false);
|
||||
m_slideSizes = hSplitter->sizes();
|
||||
hSplitter->handle(1)->setVisible(false);
|
||||
hSplitter->handle(1)->setDisabled(true);
|
||||
QList<int> sizes = QList<int>() << hSplitter->geometry().height() - 30 << 30;
|
||||
const QList<int> sizes {(hSplitter->geometry().height() - tabBarHeight), tabBarHeight};
|
||||
hSplitter->setSizes(sizes);
|
||||
setMaximumSize(maximumSize().width(), tabBarHeight);
|
||||
m_state = REDUCED;
|
||||
return;
|
||||
}
|
||||
@@ -212,6 +214,7 @@ void PropertiesWidget::setVisibility(bool visible)
|
||||
hSplitter->handle(1)->setVisible(true);
|
||||
hSplitter->setSizes(m_slideSizes);
|
||||
m_state = VISIBLE;
|
||||
setMaximumSize(maximumSize().width(), QWIDGETSIZE_MAX);
|
||||
// Force refresh
|
||||
loadDynamicData();
|
||||
}
|
||||
@@ -322,8 +325,13 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent
|
||||
|
||||
// List files in torrent
|
||||
m_propListModel->model()->setupModelData(m_torrent->info());
|
||||
if (m_propListModel->model()->rowCount() == 1)
|
||||
m_ui->filesList->setExpanded(m_propListModel->index(0, 0), true);
|
||||
|
||||
// Expand single-item folders recursively
|
||||
QModelIndex currentIndex;
|
||||
while (m_propListModel->rowCount(currentIndex) == 1) {
|
||||
currentIndex = m_propListModel->index(0, 0, currentIndex);
|
||||
m_ui->filesList->setExpanded(currentIndex, true);
|
||||
}
|
||||
|
||||
// Load file priorities
|
||||
m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
|
||||
|
@@ -100,7 +100,10 @@ void TransferListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
break;
|
||||
case TransferListModel::TR_STATUS: {
|
||||
const auto state = index.data().value<BitTorrent::TorrentState>();
|
||||
const QString errorMsg = index.data(Qt::UserRole).toString();
|
||||
QString display = getStatusString(state);
|
||||
if (state == BitTorrent::TorrentState::Error)
|
||||
display += (": " + errorMsg);
|
||||
QItemDelegate::drawDisplay(painter, opt, opt.rect, display);
|
||||
}
|
||||
break;
|
||||
|
@@ -184,7 +184,7 @@ QVariant TransferListModel::data(const QModelIndex &index, const int role) const
|
||||
case TR_PROGRESS:
|
||||
return torrent->progress();
|
||||
case TR_STATUS:
|
||||
return QVariant::fromValue(torrent->state());
|
||||
return (role == Qt::DisplayRole) ? QVariant::fromValue(torrent->state()) : torrent->error();
|
||||
case TR_SEEDS:
|
||||
return (role == Qt::DisplayRole) ? torrent->seedsCount() : torrent->totalSeedsCount();
|
||||
case TR_PEERS:
|
||||
|
@@ -857,7 +857,7 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
connect(actionPause, &QAction::triggered, this, &TransferListWidget::pauseSelectedTorrents);
|
||||
auto *actionForceStart = new QAction(UIThemeManager::instance()->getIcon("media-seek-forward"), tr("Force Resume", "Force Resume/start the torrent"), listMenu);
|
||||
connect(actionForceStart, &QAction::triggered, this, &TransferListWidget::forceStartSelectedTorrents);
|
||||
auto *actionDelete = new QAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete", "Delete the torrent"), listMenu);
|
||||
auto *actionDelete = new QAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete", "Delete the torrent"), listMenu);
|
||||
connect(actionDelete, &QAction::triggered, this, &TransferListWidget::softDeleteSelectedTorrents);
|
||||
auto *actionPreviewFile = new QAction(UIThemeManager::instance()->getIcon("view-preview"), tr("Preview file..."), listMenu);
|
||||
connect(actionPreviewFile, &QAction::triggered, this, &TransferListWidget::previewSelectedTorrents);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user