Merge pull request #13758 from glassez/coding-style

Improve coding style
This commit is contained in:
Vladimir Golovnev 2020-11-18 08:18:56 +03:00 committed by GitHub
commit e15df81351
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
147 changed files with 4454 additions and 2227 deletions

View file

@ -47,7 +47,7 @@ int myFunction(int a)
void myFunction() {} // empty body void myFunction() {} // empty body
MyClass::MyClass(int *parent) MyClass::MyClass(int *parent)
: m_parent(parent) : m_parent {parent}
{ {
// initialize // initialize
} }
@ -86,15 +86,18 @@ namespace Name
### b. Other code blocks ### b. Other code blocks
```c++ ```c++
if (condition) { if (condition)
{
// code // code
} }
for (int a = 0; a < b; ++b) { for (int a = 0; a < b; ++b)
{
// code // code
} }
switch (a) { switch (a)
{
case 1: case 1:
// blah // blah
case 2: case 2:
@ -102,18 +105,25 @@ case 2:
default: default:
// blah // blah
} }
{
// code
}
``` ```
### c. Blocks in switch's case labels ### c. Blocks in switch's case labels
```c++ ```c++
switch (var) { switch (var)
case 1: { {
case 1:
{
// declare local variables // declare local variables
// code // code
} }
break; break;
case 2: { case 2:
{
// declare local variables // declare local variables
// code // code
} }
@ -128,13 +138,16 @@ default:
The `else if`/`else` must be on their own lines: The `else if`/`else` must be on their own lines:
```c++ ```c++
if (condition) { if (condition)
{
// code // code
} }
else if (condition) { else if (condition)
{
// code // code
} }
else { else
{
// code // code
} }
``` ```
@ -155,7 +168,8 @@ However you can still choose to use the first rule.
```c++ ```c++
if (a > 0) return; if (a > 0) return;
while (p) { while (p)
{
// ... // ...
if (!b) continue; if (!b) continue;
} }
@ -178,14 +192,18 @@ else if (a > b)
else else
do(c); do(c);
if (a < b) { if (a < b)
{
do(a); do(a);
} }
else if (a > b) { // curly braces required here, then all branches should also add them else if (a > b)
{
// curly braces required here, then all branches should also add them
do(b); do(b);
do(d); do(d);
} }
else { else
{
do(c); do(c);
} }
``` ```
@ -216,10 +234,10 @@ Initialization lists should be vertical. This will allow for more easily readabl
```c++ ```c++
myClass::myClass(int a, int b, int c, int d) myClass::myClass(int a, int b, int c, int d)
: m_a(a) : m_a {a}
, m_b(b) , m_b {b}
, m_c(c) , m_c {c}
, m_d(d) , m_d {d}
{ {
// code // code
} }
@ -396,13 +414,15 @@ class ExampleWidget : public QWidget
template <typename List> template <typename List>
void doSomethingWithList(const List &list) void doSomethingWithList(const List &list)
{ {
foreach (const auto &item, list) { foreach (const auto &item, list)
{
// we don't know item type here so we use 'auto' keyword // we don't know item type here so we use 'auto' keyword
// do something with item // do something with item
} }
} }
for (auto it = container.begin(), end = container.end(); it != end; ++it) { for (auto it = container.begin(), end = container.end(); it != end; ++it)
{
// we don't need to know the exact iterator type, // we don't need to know the exact iterator type,
// because all iterators have the same interface // because all iterators have the same interface
} }
@ -420,10 +440,12 @@ class ExampleWidget : public QWidget
a = (b <= MAX_B ? b : MAX_B); a = (b <= MAX_B ? b : MAX_B);
++a; ++a;
--b; --b;
for (int a = 0; a < b; ++b) { for (int a = 0; a < b; ++b)
{
} }
// Range-based for loop, spaces before and after the colon // Range-based for loop, spaces before and after the colon
for (auto i : container) { for (auto i : container)
{
} }
// Derived class, spaces before and after the colon // Derived class, spaces before and after the colon
class Derived : public Base class Derived : public Base

View file

@ -181,12 +181,14 @@ Application::Application(int &argc, char **argv)
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType())); 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)); Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION));
if (portableModeEnabled) { if (portableModeEnabled)
{
Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir)); Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir));
if (m_commandLineArgs.relativeFastresumePaths) 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 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 { else
{
Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config))); Logger::instance()->addMessage(tr("Using config directory: %1").arg(Profile::instance()->location(SpecialFolder::Config)));
} }
} }
@ -361,11 +363,13 @@ void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) c
proc.setArguments(argList); proc.setArguments(argList);
proc.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args) proc.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
{ {
if (Preferences::instance()->isAutoRunConsoleEnabled()) { if (Preferences::instance()->isAutoRunConsoleEnabled())
{
args->flags |= CREATE_NEW_CONSOLE; args->flags |= CREATE_NEW_CONSOLE;
args->flags &= ~(CREATE_NO_WINDOW | DETACHED_PROCESS); args->flags &= ~(CREATE_NO_WINDOW | DETACHED_PROCESS);
} }
else { else
{
args->flags |= CREATE_NO_WINDOW; args->flags |= CREATE_NO_WINDOW;
args->flags &= ~(CREATE_NEW_CONSOLE | DETACHED_PROCESS); args->flags &= ~(CREATE_NEW_CONSOLE | DETACHED_PROCESS);
} }
@ -428,7 +432,8 @@ void Application::torrentFinished(BitTorrent::TorrentHandle *const torrent)
runExternalProgram(torrent); runExternalProgram(torrent);
// Mail notification // Mail notification
if (pref->isMailNotificationEnabled()) { if (pref->isMailNotificationEnabled())
{
Logger::instance()->addMessage(tr("Torrent: %1, sending mail notification").arg(torrent->name())); Logger::instance()->addMessage(tr("Torrent: %1, sending mail notification").arg(torrent->name()));
sendNotificationEmail(torrent); sendNotificationEmail(torrent);
} }
@ -455,16 +460,19 @@ void Application::allTorrentsFinished()
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
// ask confirm // ask confirm
if ((action == ShutdownDialogAction::Exit) && (pref->dontConfirmAutoExit())) { if ((action == ShutdownDialogAction::Exit) && (pref->dontConfirmAutoExit()))
{
// do nothing & skip confirm // do nothing & skip confirm
} }
else { else
{
if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return; if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return;
} }
#endif // DISABLE_GUI #endif // DISABLE_GUI
// Actually shut down // Actually shut down
if (action != ShutdownDialogAction::Exit) { if (action != ShutdownDialogAction::Exit)
{
qDebug("Preparing for auto-shutdown because all downloads are complete!"); qDebug("Preparing for auto-shutdown because all downloads are complete!");
// Disabling it for next time // Disabling it for next time
pref->setShutdownWhenDownloadsComplete(false); pref->setShutdownWhenDownloadsComplete(false);
@ -490,7 +498,8 @@ bool Application::sendParams(const QStringList &params)
void Application::processParams(const QStringList &params) void Application::processParams(const QStringList &params)
{ {
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (params.isEmpty()) { if (params.isEmpty())
{
m_window->activate(); // show UI m_window->activate(); // show UI
return; return;
} }
@ -498,42 +507,50 @@ void Application::processParams(const QStringList &params)
BitTorrent::AddTorrentParams torrentParams; BitTorrent::AddTorrentParams torrentParams;
TriStateBool skipTorrentDialog; TriStateBool skipTorrentDialog;
for (QString param : params) { for (QString param : params)
{
param = param.trimmed(); param = param.trimmed();
// Process strings indicating options specified by the user. // Process strings indicating options specified by the user.
if (param.startsWith(QLatin1String("@savePath="))) { if (param.startsWith(QLatin1String("@savePath=")))
{
torrentParams.savePath = param.mid(10); torrentParams.savePath = param.mid(10);
continue; continue;
} }
if (param.startsWith(QLatin1String("@addPaused="))) { if (param.startsWith(QLatin1String("@addPaused=")))
{
torrentParams.addPaused = param.midRef(11).toInt() ? TriStateBool::True : TriStateBool::False; torrentParams.addPaused = param.midRef(11).toInt() ? TriStateBool::True : TriStateBool::False;
continue; continue;
} }
if (param == QLatin1String("@skipChecking")) { if (param == QLatin1String("@skipChecking"))
{
torrentParams.skipChecking = true; torrentParams.skipChecking = true;
continue; continue;
} }
if (param.startsWith(QLatin1String("@category="))) { if (param.startsWith(QLatin1String("@category=")))
{
torrentParams.category = param.mid(10); torrentParams.category = param.mid(10);
continue; continue;
} }
if (param == QLatin1String("@sequential")) { if (param == QLatin1String("@sequential"))
{
torrentParams.sequential = true; torrentParams.sequential = true;
continue; continue;
} }
if (param == QLatin1String("@firstLastPiecePriority")) { if (param == QLatin1String("@firstLastPiecePriority"))
{
torrentParams.firstLastPiecePriority = true; torrentParams.firstLastPiecePriority = true;
continue; continue;
} }
if (param.startsWith(QLatin1String("@skipDialog="))) { if (param.startsWith(QLatin1String("@skipDialog=")))
{
skipTorrentDialog = param.midRef(12).toInt() ? TriStateBool::True : TriStateBool::False; skipTorrentDialog = param.midRef(12).toInt() ? TriStateBool::True : TriStateBool::False;
continue; continue;
} }
@ -561,7 +578,8 @@ int Application::exec(const QStringList &params)
Net::DownloadManager::initInstance(); Net::DownloadManager::initInstance();
IconProvider::initInstance(); IconProvider::initInstance();
try { try
{
BitTorrent::Session::initInstance(); BitTorrent::Session::initInstance();
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished); connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection); connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
@ -581,7 +599,8 @@ int Application::exec(const QStringList &params)
new RSS::Session; // create RSS::Session singleton new RSS::Session; // create RSS::Session singleton
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
} }
catch (const RuntimeError &err) { catch (const RuntimeError &err)
{
#ifdef DISABLE_GUI #ifdef DISABLE_GUI
fprintf(stderr, "%s", err.what()); fprintf(stderr, "%s", err.what());
#else #else
@ -605,7 +624,8 @@ int Application::exec(const QStringList &params)
.arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n'; .arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n';
printf("%s", qUtf8Printable(mesg)); printf("%s", qUtf8Printable(mesg));
if (pref->getWebUIPassword() == "ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==") { if (pref->getWebUIPassword() == "ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")
{
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + '\n' const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + '\n'
+ tr("The Web UI administrator password is still the default one: %1").arg("adminadmin") + '\n' + tr("The Web UI administrator password is still the default one: %1").arg("adminadmin") + '\n'
+ tr("This is a security risk, please consider changing your password from program preferences.") + '\n'; + tr("This is a security risk, please consider changing your password from program preferences.") + '\n';
@ -623,7 +643,8 @@ int Application::exec(const QStringList &params)
BitTorrent::Session::instance()->startUpTorrents(); BitTorrent::Session::instance()->startUpTorrents();
m_paramsQueue = params + m_paramsQueue; m_paramsQueue = params + m_paramsQueue;
if (!m_paramsQueue.isEmpty()) { if (!m_paramsQueue.isEmpty())
{
processParams(m_paramsQueue); processParams(m_paramsQueue);
m_paramsQueue.clear(); m_paramsQueue.clear();
} }
@ -639,7 +660,8 @@ bool Application::isRunning()
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
bool Application::event(QEvent *ev) bool Application::event(QEvent *ev)
{ {
if (ev->type() == QEvent::FileOpen) { if (ev->type() == QEvent::FileOpen)
{
QString path = static_cast<QFileOpenEvent *>(ev)->file(); QString path = static_cast<QFileOpenEvent *>(ev)->file();
if (path.isEmpty()) if (path.isEmpty())
// Get the url instead // Get the url instead
@ -651,7 +673,8 @@ bool Application::event(QEvent *ev)
m_paramsQueue.append(path); m_paramsQueue.append(path);
return true; return true;
} }
else { else
{
return BaseApplication::event(ev); return BaseApplication::event(ev);
} }
} }
@ -679,11 +702,13 @@ void Application::initializeTranslation()
installTranslator(&m_translator); installTranslator(&m_translator);
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (localeStr.startsWith("ar") || localeStr.startsWith("he")) { if (localeStr.startsWith("ar") || localeStr.startsWith("he"))
{
qDebug("Right to Left mode"); qDebug("Right to Left mode");
setLayoutDirection(Qt::RightToLeft); setLayoutDirection(Qt::RightToLeft);
} }
else { else
{
setLayoutDirection(Qt::LeftToRight); setLayoutDirection(Qt::LeftToRight);
} }
#endif #endif
@ -725,7 +750,8 @@ void Application::cleanup()
return; return;
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (m_window) { if (m_window)
{
// Hide the window and don't leave it on screen as // Hide the window and don't leave it on screen as
// unresponsive. Also for Windows take the WinId // unresponsive. Also for Windows take the WinId
// after it's hidden, because hide() may cause a // after it's hidden, because hide() may cause a
@ -769,7 +795,8 @@ void Application::cleanup()
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath()); Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (m_window) { if (m_window)
{
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId())); ::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
#endif // Q_OS_WIN #endif // Q_OS_WIN
@ -780,7 +807,8 @@ void Application::cleanup()
Profile::freeInstance(); Profile::freeInstance();
if (m_shutdownAct != ShutdownDialogAction::Exit) { if (m_shutdownAct != ShutdownDialogAction::Exit)
{
qDebug() << "Sending computer shutdown/suspend/hibernate signal..."; qDebug() << "Sending computer shutdown/suspend/hibernate signal...";
Utils::Misc::shutdownComputer(m_shutdownAct); Utils::Misc::shutdownComputer(m_shutdownAct);
} }

View file

@ -46,16 +46,20 @@ ApplicationInstanceManager::ApplicationInstanceManager(const QString &appId, QOb
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
auto sharedMem = new QSharedMemory {appId + QLatin1String {"-shared-memory-key"}, this}; auto sharedMem = new QSharedMemory {appId + QLatin1String {"-shared-memory-key"}, this};
if (m_isFirstInstance) { if (m_isFirstInstance)
{
// First instance creates shared memory and store PID // First instance creates shared memory and store PID
if (sharedMem->create(sizeof(DWORD)) && sharedMem->lock()) { if (sharedMem->create(sizeof(DWORD)) && sharedMem->lock())
{
*(static_cast<DWORD *>(sharedMem->data())) = ::GetCurrentProcessId(); *(static_cast<DWORD *>(sharedMem->data())) = ::GetCurrentProcessId();
sharedMem->unlock(); sharedMem->unlock();
} }
} }
else { else
{
// Later instances attach to shared memory and retrieve PID // Later instances attach to shared memory and retrieve PID
if (sharedMem->attach() && sharedMem->lock()) { if (sharedMem->attach() && sharedMem->lock())
{
::AllowSetForegroundWindow(*(static_cast<DWORD *>(sharedMem->data()))); ::AllowSetForegroundWindow(*(static_cast<DWORD *>(sharedMem->data())));
sharedMem->unlock(); sharedMem->unlock();
} }

View file

@ -217,7 +217,8 @@ namespace
bool ok; bool ok;
int res = val.toInt(&ok); int res = val.toInt(&ok);
if (!ok) { if (!ok)
{
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'") qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
.arg(envVarName(), val); .arg(envVarName(), val);
return defaultValue; return defaultValue;
@ -257,16 +258,20 @@ namespace
{ {
QStringList parts = arg.split(QLatin1Char('=')); QStringList parts = arg.split(QLatin1Char('='));
if (parts.size() == 1) { if (parts.size() == 1)
{
return TriStateBool(m_defaultValue); return TriStateBool(m_defaultValue);
} }
if (parts.size() == 2) { if (parts.size() == 2)
{
QString val = parts[1]; QString val = parts[1];
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) { if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1")))
{
return TriStateBool::True; return TriStateBool::True;
} }
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) { if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
{
return TriStateBool::False; return TriStateBool::False;
} }
} }
@ -281,16 +286,20 @@ namespace
{ {
const QString val = env.value(envVarName(), "-1"); const QString val = env.value(envVarName(), "-1");
if (val.isEmpty()) { if (val.isEmpty())
{
return TriStateBool(m_defaultValue); return TriStateBool(m_defaultValue);
} }
if (val == QLatin1String("-1")) { if (val == QLatin1String("-1"))
{
return TriStateBool::Undefined; return TriStateBool::Undefined;
} }
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) { if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1")))
{
return TriStateBool::True; return TriStateBool::True;
} }
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) { if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
{
return TriStateBool::False; return TriStateBool::False;
} }
@ -365,10 +374,12 @@ QStringList QBtCommandLineParameters::paramList() const
if (!savePath.isEmpty()) if (!savePath.isEmpty())
result.append(QLatin1String("@savePath=") + savePath); result.append(QLatin1String("@savePath=") + savePath);
if (addPaused == TriStateBool::True) { if (addPaused == TriStateBool::True)
{
result.append(QLatin1String("@addPaused=1")); result.append(QLatin1String("@addPaused=1"));
} }
else if (addPaused == TriStateBool::False) { else if (addPaused == TriStateBool::False)
{
result.append(QLatin1String("@addPaused=0")); result.append(QLatin1String("@addPaused=0"));
} }
@ -384,10 +395,12 @@ QStringList QBtCommandLineParameters::paramList() const
if (firstLastPiecePriority) if (firstLastPiecePriority)
result.append(QLatin1String("@firstLastPiecePriority")); result.append(QLatin1String("@firstLastPiecePriority"));
if (skipDialog == TriStateBool::True) { if (skipDialog == TriStateBool::True)
{
result.append(QLatin1String("@skipDialog=1")); result.append(QLatin1String("@skipDialog=1"));
} }
else if (skipDialog == TriStateBool::False) { else if (skipDialog == TriStateBool::False)
{
result.append(QLatin1String("@skipDialog=0")); result.append(QLatin1String("@skipDialog=0"));
} }
@ -399,72 +412,91 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
{ {
QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()}; QBtCommandLineParameters result {QProcessEnvironment::systemEnvironment()};
for (int i = 1; i < args.count(); ++i) { for (int i = 1; i < args.count(); ++i)
{
const QString &arg = args[i]; const QString &arg = args[i];
if ((arg.startsWith("--") && !arg.endsWith(".torrent")) if ((arg.startsWith("--") && !arg.endsWith(".torrent"))
|| (arg.startsWith('-') && (arg.size() == 2))) { || (arg.startsWith('-') && (arg.size() == 2)))
{
// Parse known parameters // Parse known parameters
if (arg == SHOW_HELP_OPTION) { if (arg == SHOW_HELP_OPTION)
{
result.showHelp = true; result.showHelp = true;
} }
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI) #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
else if (arg == SHOW_VERSION_OPTION) { else if (arg == SHOW_VERSION_OPTION)
{
result.showVersion = true; result.showVersion = true;
} }
#endif #endif
else if (arg == WEBUI_PORT_OPTION) { else if (arg == WEBUI_PORT_OPTION)
{
result.webUiPort = WEBUI_PORT_OPTION.value(arg); result.webUiPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535)) if ((result.webUiPort < 1) || (result.webUiPort > 65535))
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).") throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
.arg(QLatin1String("--webui-port"))); .arg(QLatin1String("--webui-port")));
} }
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
else if (arg == NO_SPLASH_OPTION) { else if (arg == NO_SPLASH_OPTION)
{
result.noSplash = true; result.noSplash = true;
} }
#elif !defined(Q_OS_WIN) #elif !defined(Q_OS_WIN)
else if (arg == DAEMON_OPTION) { else if (arg == DAEMON_OPTION)
{
result.shouldDaemonize = true; result.shouldDaemonize = true;
} }
#endif #endif
else if (arg == PROFILE_OPTION) { else if (arg == PROFILE_OPTION)
{
result.profileDir = PROFILE_OPTION.value(arg); result.profileDir = PROFILE_OPTION.value(arg);
} }
else if (arg == RELATIVE_FASTRESUME) { else if (arg == RELATIVE_FASTRESUME)
{
result.relativeFastresumePaths = true; result.relativeFastresumePaths = true;
} }
else if (arg == CONFIGURATION_OPTION) { else if (arg == CONFIGURATION_OPTION)
{
result.configurationName = CONFIGURATION_OPTION.value(arg); result.configurationName = CONFIGURATION_OPTION.value(arg);
} }
else if (arg == SAVE_PATH_OPTION) { else if (arg == SAVE_PATH_OPTION)
{
result.savePath = SAVE_PATH_OPTION.value(arg); result.savePath = SAVE_PATH_OPTION.value(arg);
} }
else if (arg == PAUSED_OPTION) { else if (arg == PAUSED_OPTION)
{
result.addPaused = PAUSED_OPTION.value(arg); result.addPaused = PAUSED_OPTION.value(arg);
} }
else if (arg == SKIP_HASH_CHECK_OPTION) { else if (arg == SKIP_HASH_CHECK_OPTION)
{
result.skipChecking = true; result.skipChecking = true;
} }
else if (arg == CATEGORY_OPTION) { else if (arg == CATEGORY_OPTION)
{
result.category = CATEGORY_OPTION.value(arg); result.category = CATEGORY_OPTION.value(arg);
} }
else if (arg == SEQUENTIAL_OPTION) { else if (arg == SEQUENTIAL_OPTION)
{
result.sequential = true; result.sequential = true;
} }
else if (arg == FIRST_AND_LAST_OPTION) { else if (arg == FIRST_AND_LAST_OPTION)
{
result.firstLastPiecePriority = true; result.firstLastPiecePriority = true;
} }
else if (arg == SKIP_DIALOG_OPTION) { else if (arg == SKIP_DIALOG_OPTION)
{
result.skipDialog = SKIP_DIALOG_OPTION.value(arg); result.skipDialog = SKIP_DIALOG_OPTION.value(arg);
} }
else { else
{
// Unknown argument // Unknown argument
result.unknownParameter = arg; result.unknownParameter = arg;
break; break;
} }
} }
else { else
{
QFileInfo torrentPath; QFileInfo torrentPath;
torrentPath.setFile(arg); torrentPath.setFile(arg);
@ -495,11 +527,14 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
QStringList lines = {words.first()}; QStringList lines = {words.first()};
int currentLineMaxLength = wrapAtColumn - initialIndentation; int currentLineMaxLength = wrapAtColumn - initialIndentation;
for (const QString &word : asConst(words.mid(1))) { for (const QString &word : asConst(words.mid(1)))
if (lines.last().length() + word.length() + 1 < currentLineMaxLength) { {
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
{
lines.last().append(' ' + word); lines.last().append(' ' + word);
} }
else { else
{
lines.append(QString(initialIndentation, ' ') + word); lines.append(QString(initialIndentation, ' ') + word);
currentLineMaxLength = wrapAtColumn; currentLineMaxLength = wrapAtColumn;
} }

View file

@ -73,7 +73,8 @@ void FileLogger::changePath(const QString &newPath)
dir.mkpath(newPath); dir.mkpath(newPath);
const QString tmpPath = dir.absoluteFilePath("qbittorrent.log"); const QString tmpPath = dir.absoluteFilePath("qbittorrent.log");
if (tmpPath != m_path) { if (tmpPath != m_path)
{
m_path = tmpPath; m_path = tmpPath;
closeLogFile(); closeLogFile();
@ -89,9 +90,11 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
const QFileInfoList fileList = dir.entryInfoList(QStringList("qbittorrent.log.bak*") const QFileInfoList fileList = dir.entryInfoList(QStringList("qbittorrent.log.bak*")
, (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed)); , (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed));
for (const QFileInfo &file : fileList) { for (const QFileInfo &file : fileList)
{
QDateTime modificationDate = file.lastModified(); QDateTime modificationDate = file.lastModified();
switch (ageType) { switch (ageType)
{
case DAYS: case DAYS:
modificationDate = modificationDate.addDays(age); modificationDate = modificationDate.addDays(age);
break; break;
@ -124,7 +127,8 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
QTextStream stream(&m_logFile); QTextStream stream(&m_logFile);
stream.setCodec("UTF-8"); stream.setCodec("UTF-8");
switch (msg.type) { switch (msg.type)
{
case Log::INFO: case Log::INFO:
stream << "(I) "; stream << "(I) ";
break; break;
@ -140,12 +144,14 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
stream << QDateTime::fromMSecsSinceEpoch(msg.timestamp).toString(Qt::ISODate) << " - " << msg.message << '\n'; stream << QDateTime::fromMSecsSinceEpoch(msg.timestamp).toString(Qt::ISODate) << " - " << msg.message << '\n';
if (m_backup && (m_logFile.size() >= m_maxSize)) { if (m_backup && (m_logFile.size() >= m_maxSize))
{
closeLogFile(); closeLogFile();
int counter = 0; int counter = 0;
QString backupLogFilename = m_path + ".bak"; QString backupLogFilename = m_path + ".bak";
while (QFile::exists(backupLogFilename)) { while (QFile::exists(backupLogFilename))
{
++counter; ++counter;
backupLogFilename = m_path + ".bak" + QString::number(counter); backupLogFilename = m_path + ".bak" + QString::number(counter);
} }
@ -153,7 +159,8 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
QFile::rename(m_path, backupLogFilename); QFile::rename(m_path, backupLogFilename);
openLogFile(); openLogFile();
} }
else { else
{
if (!m_flusher.isActive()) if (!m_flusher.isActive())
m_flusher.start(); m_flusher.start();
} }
@ -168,7 +175,8 @@ void FileLogger::flushLog()
void FileLogger::openLogFile() void FileLogger::openLogFile()
{ {
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text) if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)
|| !m_logFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) { || !m_logFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner))
{
m_logFile.close(); m_logFile.close();
LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL); LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
} }

View file

@ -92,7 +92,8 @@ void sigNormalHandler(int signum);
void sigAbnormalHandler(int signum); void sigAbnormalHandler(int signum);
#endif #endif
// sys_signame[] is only defined in BSD // sys_signame[] is only defined in BSD
const char *const sysSigName[] = { const char *const sysSigName[] =
{
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
"", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "", "", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
"", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "", "", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
@ -141,19 +142,23 @@ int main(int argc, char *argv[])
Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif #endif
try { try
{
// Create Application // Create Application
auto app = std::make_unique<Application>(argc, argv); auto app = std::make_unique<Application>(argc, argv);
const QBtCommandLineParameters params = app->commandLineArgs(); const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty()) { if (!params.unknownParameter.isEmpty())
{
throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.", throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
"--random-parameter is an unknown command line parameter.") "--random-parameter is an unknown command line parameter.")
.arg(params.unknownParameter)); .arg(params.unknownParameter));
} }
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI) #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
if (params.showVersion) { if (params.showVersion)
if (isOneArg) { {
if (isOneArg)
{
displayVersion(); displayVersion();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@ -161,8 +166,10 @@ int main(int argc, char *argv[])
.arg(QLatin1String("-v (or --version)"))); .arg(QLatin1String("-v (or --version)")));
} }
#endif #endif
if (params.showHelp) { if (params.showHelp)
if (isOneArg) { {
if (isOneArg)
{
displayUsage(argv[0]); displayUsage(argv[0]);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@ -175,7 +182,8 @@ int main(int argc, char *argv[])
fprintf(stderr, "Couldn't set environment variable...\n"); fprintf(stderr, "Couldn't set environment variable...\n");
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal(); const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
if (firstTimeUser) { if (firstTimeUser)
{
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
if (!userAgreesWithLegalNotice()) if (!userAgreesWithLegalNotice())
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -195,9 +203,11 @@ int main(int argc, char *argv[])
} }
// Check if qBittorrent is already running for this user // Check if qBittorrent is already running for this user
if (app->isRunning()) { if (app->isRunning())
{
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN) #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize) { if (params.shouldDaemonize)
{
throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.") throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
.arg(QLatin1String("-d (or --daemon)"))); .arg(QLatin1String("-d (or --daemon)")));
} }
@ -242,7 +252,8 @@ int main(int argc, char *argv[])
app->setAttribute(Qt::AA_DontShowIconsInMenus); app->setAttribute(Qt::AA_DontShowIconsInMenus);
#endif #endif
if (!firstTimeUser) { if (!firstTimeUser)
{
handleChangedDefaults(DefaultPreferencesMode::Legacy); handleChangedDefaults(DefaultPreferencesMode::Legacy);
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
@ -256,21 +267,26 @@ int main(int argc, char *argv[])
&& isatty(fileno(stdout)))) return EXIT_FAILURE; && isatty(fileno(stdout)))) return EXIT_FAILURE;
#endif #endif
} }
else { else
{
handleChangedDefaults(DefaultPreferencesMode::Current); handleChangedDefaults(DefaultPreferencesMode::Current);
} }
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN) #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize) { if (params.shouldDaemonize)
{
app.reset(); // Destroy current application app.reset(); // Destroy current application
if (daemon(1, 0) == 0) { if (daemon(1, 0) == 0)
{
app = std::make_unique<Application>(argc, argv); app = std::make_unique<Application>(argc, argv);
if (app->isRunning()) { if (app->isRunning())
{
// Another instance had time to start. // Another instance had time to start.
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
else { else
{
qCritical("Something went wrong while daemonizing, exiting..."); qCritical("Something went wrong while daemonizing, exiting...");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -289,7 +305,8 @@ int main(int argc, char *argv[])
return app->exec(params.paramList()); return app->exec(params.paramList());
} }
catch (const CommandLineParameterError &er) { catch (const CommandLineParameterError &er)
{
displayBadArgMessage(er.messageForUser()); displayBadArgMessage(er.messageForUser());
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -300,10 +317,12 @@ void reportToUser(const char *str)
{ {
const size_t strLen = strlen(str); const size_t strLen = strlen(str);
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen)) { if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
{
const auto dummy = write(STDOUT_FILENO, str, strLen); const auto dummy = write(STDOUT_FILENO, str, strLen);
#else #else
if (_write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen)) { if (_write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
{
const auto dummy = _write(STDOUT_FILENO, str, strLen); const auto dummy = _write(STDOUT_FILENO, str, strLen);
#endif #endif
Q_UNUSED(dummy); Q_UNUSED(dummy);
@ -405,7 +424,8 @@ bool userAgreesWithLegalNotice()
printf("%s", qUtf8Printable(eula)); printf("%s", qUtf8Printable(eula));
const char ret = getchar(); // Read pressed key const char ret = getchar(); // Read pressed key
if ((ret == 'y') || (ret == 'Y')) { if ((ret == 'y') || (ret == 'Y'))
{
// Save the answer // Save the answer
pref->setAcceptedLegal(true); pref->setAcceptedLegal(true);
return true; return true;
@ -419,7 +439,8 @@ bool userAgreesWithLegalNotice()
msgBox.show(); // Need to be shown or to moveToCenter does not work msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox)); msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec(); msgBox.exec();
if (msgBox.clickedButton() == agreeButton) { if (msgBox.clickedButton() == agreeButton)
{
// Save the answer // Save the answer
pref->setAcceptedLegal(true); pref->setAcceptedLegal(true);
return true; return true;

View file

@ -100,7 +100,8 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
, id(appId) , id(appId)
{ {
QString prefix = id; QString prefix = id;
if (id.isEmpty()) { if (id.isEmpty())
{
id = QCoreApplication::applicationFilePath(); id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
id = id.toLower(); id = id.toLower();
@ -143,7 +144,8 @@ bool QtLocalPeer::isClient()
bool res = server->listen(socketName); bool res = server->listen(socketName);
#if defined(Q_OS_UNIX) #if defined(Q_OS_UNIX)
// ### Workaround // ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { if (!res && server->serverError() == QAbstractSocket::AddressInUseError)
{
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName); res = server->listen(socketName);
} }
@ -161,7 +163,8 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
QLocalSocket socket; QLocalSocket socket;
bool connOk = false; bool connOk = false;
for(int i = 0; i < 2; i++) { for(int i = 0; i < 2; i++)
{
// Try twice, in case the other instance is just starting up // Try twice, in case the other instance is just starting up
socket.connectToServer(socketName); socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2); connOk = socket.waitForConnected(timeout/2);
@ -182,7 +185,8 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
QDataStream ds(&socket); QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size()); ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout); bool res = socket.waitForBytesWritten(timeout);
if (res) { if (res)
{
res &= socket.waitForReadyRead(timeout); // wait for ack res &= socket.waitForReadyRead(timeout); // wait for ack
if (res) if (res)
res &= (socket.read(qstrlen(ack)) == ack); res &= (socket.read(qstrlen(ack)) == ack);
@ -201,8 +205,10 @@ void QtLocalPeer::receiveConnection()
if (!socket) if (!socket)
return; return;
while (true) { while (true)
if (socket->state() == QLocalSocket::UnconnectedState) { {
if (socket->state() == QLocalSocket::UnconnectedState)
{
qWarning("QtLocalPeer: Peer disconnected"); qWarning("QtLocalPeer: Peer disconnected");
delete socket; delete socket;
return; return;
@ -216,7 +222,8 @@ void QtLocalPeer::receiveConnection()
QByteArray uMsg; QByteArray uMsg;
quint32 remaining; quint32 remaining;
ds >> remaining; ds >> remaining;
if (remaining > 65535) { if (remaining > 65535)
{
// drop suspiciously large data // drop suspiciously large data
delete socket; delete socket;
return; return;
@ -225,12 +232,14 @@ void QtLocalPeer::receiveConnection()
uMsg.resize(remaining); uMsg.resize(remaining);
int got = 0; int got = 0;
char* uMsgBuf = uMsg.data(); char* uMsgBuf = uMsg.data();
do { do
{
got = ds.readRawData(uMsgBuf, remaining); got = ds.readRawData(uMsgBuf, remaining);
remaining -= got; remaining -= got;
uMsgBuf += got; uMsgBuf += got;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000)); } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0) { if (got < 0)
{
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
delete socket; delete socket;
return; return;

View file

@ -22,7 +22,8 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
// retrieve current stack addresses // retrieve current stack addresses
int addrlen = backtrace(addrlist.data(), addrlist.size()); int addrlen = backtrace(addrlist.data(), addrlist.size());
if (addrlen == 0) { if (addrlen == 0)
{
fprintf(out, " <empty, possibly corrupt>\n"); fprintf(out, " <empty, possibly corrupt>\n");
return; return;
} }
@ -38,27 +39,33 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
int functionNamesFound = 0; int functionNamesFound = 0;
// iterate over the returned symbol lines. skip the first, it is the // iterate over the returned symbol lines. skip the first, it is the
// address of this function. // address of this function.
for (int i = 2; i < addrlen; i++) { for (int i = 2; i < addrlen; i++)
{
char *begin_name = 0, *begin_offset = 0, *end_offset = 0; char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
// find parentheses and +address offset surrounding the mangled name: // find parentheses and +address offset surrounding the mangled name:
// ./module(function+0x15c) [0x8048a6d] // ./module(function+0x15c) [0x8048a6d]
// fprintf(out, "%s TT\n", symbollist[i]); // fprintf(out, "%s TT\n", symbollist[i]);
for (char *p = symbollist[i]; *p; ++p) { for (char *p = symbollist[i]; *p; ++p)
if (*p == '(') { {
if (*p == '(')
{
begin_name = p; begin_name = p;
} }
else if (*p == '+') { else if (*p == '+')
{
begin_offset = p; begin_offset = p;
} }
else if ((*p == ')') && begin_offset) { else if ((*p == ')') && begin_offset)
{
end_offset = p; end_offset = p;
break; break;
} }
} }
if (begin_name && begin_offset && end_offset if (begin_name && begin_offset && end_offset
&& (begin_name < begin_offset)) { && (begin_name < begin_offset))
{
*begin_name++ = '\0'; *begin_name++ = '\0';
*begin_offset++ = '\0'; *begin_offset++ = '\0';
*end_offset = '\0'; *end_offset = '\0';
@ -70,12 +77,14 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
int status; int status;
char *ret = abi::__cxa_demangle(begin_name, char *ret = abi::__cxa_demangle(begin_name,
funcname, &funcnamesize, &status); funcname, &funcnamesize, &status);
if (status == 0) { if (status == 0)
{
funcname = ret; // use possibly realloc()-ed string funcname = ret; // use possibly realloc()-ed string
fprintf(out, " %s : %s+%s %s\n", fprintf(out, " %s : %s+%s %s\n",
symbollist[i], funcname, begin_offset, ++end_offset); symbollist[i], funcname, begin_offset, ++end_offset);
} }
else { else
{
// demangling failed. Output function name as a C function with // demangling failed. Output function name as a C function with
// no arguments. // no arguments.
fprintf(out, " %s : %s()+%s %s\n", fprintf(out, " %s : %s()+%s %s\n",
@ -83,17 +92,20 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
} }
++functionNamesFound; ++functionNamesFound;
} }
else { else
{
// couldn't parse the line? print the whole line. // couldn't parse the line? print the whole line.
fprintf(out, " %s\n", symbollist[i]); fprintf(out, " %s\n", symbollist[i]);
} }
} }
if (!functionNamesFound) { if (!functionNamesFound)
{
fprintf(out, "There were no function names found in the stack trace\n." fprintf(out, "There were no function names found in the stack trace\n."
"Seems like debug symbols are not installed, and the stack trace is useless.\n"); "Seems like debug symbols are not installed, and the stack trace is useless.\n");
} }
if (functionNamesFound < addrlen - 2) { if (functionNamesFound < addrlen - 2)
{
fprintf(out, "Consider installing debug symbols for packages containing files with empty" fprintf(out, "Consider installing debug symbols for packages containing files with empty"
" function names (i.e. empty braces \"()\") to make your stack trace more useful\n"); " function names (i.e. empty braces \"()\") to make your stack trace more useful\n");
} }

View file

@ -54,7 +54,8 @@ void straceWin::demangle(QString& str)
int status = 0; int status = 0;
size_t outSz = 0; size_t outSz = 0;
char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status); char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status);
if (status == 0) { if (status == 0)
{
str = QString::fromLocal8Bit(demangled_name); str = QString::fromLocal8Bit(demangled_name);
if (outSz > 0) if (outSz > 0)
free(demangled_name); free(demangled_name);
@ -92,7 +93,8 @@ BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVO
IMAGEHLP_MODULE64 mod; IMAGEHLP_MODULE64 mod;
EnumModulesContext* context = (EnumModulesContext*)UserContext; EnumModulesContext* context = (EnumModulesContext*)UserContext;
mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); mod.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
if(SymGetModuleInfo64(context->hProcess, BaseOfDll, &mod)) { if(SymGetModuleInfo64(context->hProcess, BaseOfDll, &mod))
{
QString moduleBase = QString::fromLatin1("0x%1").arg(BaseOfDll, 16, 16, QLatin1Char('0')); QString moduleBase = QString::fromLatin1("0x%1").arg(BaseOfDll, 16, 16, QLatin1Char('0'));
QString line = QString::fromLatin1("%1 %2 Image: %3") QString line = QString::fromLatin1("%1 %2 Image: %3")
.arg(mod.ModuleName, -25) .arg(mod.ModuleName, -25)
@ -101,7 +103,8 @@ BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVO
context->stream << line << '\n'; context->stream << line << '\n';
QString pdbName(mod.LoadedPdbName); QString pdbName(mod.LoadedPdbName);
if(!pdbName.isEmpty()) { if(!pdbName.isEmpty())
{
QString line2 = QString::fromLatin1("%1 %2") QString line2 = QString::fromLatin1("%1 %2")
.arg("", 35) .arg("", 35)
.arg(pdbName); .arg(pdbName);
@ -126,7 +129,8 @@ bool straceWin::makeRelativePath(const QString& dir, QString& file)
if (!d.isEmpty() && (d[d.length() - 1] != separator)) if (!d.isEmpty() && (d[d.length() - 1] != separator))
d += separator; d += separator;
if (f.startsWith(d, Qt::CaseInsensitive)) { if (f.startsWith(d, Qt::CaseInsensitive))
{
f.remove(0, d.length()); f.remove(0, d.length());
file.swap(f); file.swap(f);
@ -142,7 +146,8 @@ QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD dwDisplacement = 0; DWORD dwDisplacement = 0;
if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line)) { if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line))
{
QString path(line.FileName); QString path(line.FileName);
#if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH #if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH
@ -159,7 +164,8 @@ QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
#endif #endif
#ifdef STACKTRACE_WIN_MAKEFILE_PATH #ifdef STACKTRACE_WIN_MAKEFILE_PATH
if (!success) { if (!success)
{
QString targetPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH)); QString targetPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH));
makeRelativePath(targetPath, path); makeRelativePath(targetPath, path);
} }
@ -201,7 +207,8 @@ const QString straceWin::getBacktrace()
: //no input : //no input
: "eax"); : "eax");
#else #else
_asm { _asm
{
Label: Label:
mov [Context.Ebp], ebp; mov [Context.Ebp], ebp;
mov [Context.Esp], esp; mov [Context.Esp], esp;
@ -269,15 +276,18 @@ const QString straceWin::getBacktrace()
int i = 0; int i = 0;
while(StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context, NULL, NULL, NULL, NULL)) { while(StackWalk64(MachineType, hProcess, hThread, &StackFrame, &Context, NULL, NULL, NULL, NULL))
{
if(i == 128) if(i == 128)
break; break;
loadHelpStackFrame(ihsf, StackFrame); loadHelpStackFrame(ihsf, StackFrame);
if(StackFrame.AddrPC.Offset != 0) { // Valid frame. if(StackFrame.AddrPC.Offset != 0)
{ // Valid frame.
QString fileName("???"); QString fileName("???");
if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod)) { if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod))
{
fileName = QString(mod.ImageName); fileName = QString(mod.ImageName);
int slashPos = fileName.lastIndexOf('\\'); int slashPos = fileName.lastIndexOf('\\');
if(slashPos != -1) if(slashPos != -1)
@ -285,7 +295,8 @@ const QString straceWin::getBacktrace()
} }
QString funcName; QString funcName;
QString sourceFile; QString sourceFile;
if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol)) { if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol))
{
funcName = QString(pSymbol->Name); funcName = QString(pSymbol->Name);
#ifdef __MINGW32__ #ifdef __MINGW32__
demangle(funcName); demangle(funcName);
@ -295,7 +306,8 @@ const QString straceWin::getBacktrace()
// decrease the query address by one byte to point somewhere in the CALL instruction byte sequence // decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1); sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
} }
else { else
{
funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0')); funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0'));
} }
SymSetContext(hProcess, &ihsf, NULL); SymSetContext(hProcess, &ihsf, NULL);
@ -325,7 +337,8 @@ const QString straceWin::getBacktrace()
logStream << debugLine << '\n'; logStream << debugLine << '\n';
i++; i++;
} }
else { else
{
break; // we're at the end. break; // we're at the end.
} }
} }

View file

@ -39,7 +39,8 @@ namespace
{ {
void exportWebUIHttpsFiles() void exportWebUIHttpsFiles()
{ {
const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath) { const auto migrate = [](const QString &oldKey, const QString &newKey, const QString &savePath)
{
SettingsStorage *settingsStorage {SettingsStorage::instance()}; SettingsStorage *settingsStorage {SettingsStorage::instance()};
const QByteArray oldData {settingsStorage->loadValue(oldKey).toByteArray()}; const QByteArray oldData {settingsStorage->loadValue(oldKey).toByteArray()};
const QString newData {settingsStorage->loadValue(newKey).toString()}; const QString newData {settingsStorage->loadValue(newKey).toString()};
@ -49,11 +50,13 @@ namespace
return; return;
QFile file(savePath); QFile file(savePath);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly))
{
LogMsg(errorMsgFormat.arg(savePath, file.errorString()) , Log::WARNING); LogMsg(errorMsgFormat.arg(savePath, file.errorString()) , Log::WARNING);
return; return;
} }
if (file.write(oldData) != oldData.size()) { if (file.write(oldData) != oldData.size())
{
file.close(); file.close();
Utils::Fs::forceRemove(savePath); Utils::Fs::forceRemove(savePath);
LogMsg(errorMsgFormat.arg(savePath, QLatin1String("Write incomplete.")) , Log::WARNING); LogMsg(errorMsgFormat.arg(savePath, QLatin1String("Write incomplete.")) , Log::WARNING);
@ -92,12 +95,14 @@ void handleChangedDefaults(const DefaultPreferencesMode mode)
QVariant current; QVariant current;
}; };
const QVector<DefaultValue> changedDefaults { const QVector<DefaultValue> changedDefaults
{
{QLatin1String {"BitTorrent/Session/QueueingSystemEnabled"}, true, false} {QLatin1String {"BitTorrent/Session/QueueingSystemEnabled"}, true, false}
}; };
SettingsStorage *settingsStorage {SettingsStorage::instance()}; SettingsStorage *settingsStorage {SettingsStorage::instance()};
for (auto it = changedDefaults.cbegin(); it != changedDefaults.cend(); ++it) { for (auto it = changedDefaults.cbegin(); it != changedDefaults.cend(); ++it)
{
if (settingsStorage->loadValue(it->name).isNull()) if (settingsStorage->loadValue(it->name).isNull())
settingsStorage->storeValue(it->name, (mode == DefaultPreferencesMode::Legacy ? it->legacy : it->current)); settingsStorage->storeValue(it->name, (mode == DefaultPreferencesMode::Legacy ? it->legacy : it->current));
} }

View file

@ -38,7 +38,8 @@ AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *pa
, m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock"))) , m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock")))
{ {
if (!m_storageDir.mkpath(m_storageDir.absolutePath())) if (!m_storageDir.mkpath(m_storageDir.absolutePath()))
throw AsyncFileStorageError {tr("Could not create directory '%1'.") throw AsyncFileStorageError
{tr("Could not create directory '%1'.")
.arg(m_storageDir.absolutePath())}; .arg(m_storageDir.absolutePath())};
// TODO: This folder locking approach does not work for UNIX systems. Implement it. // TODO: This folder locking approach does not work for UNIX systems. Implement it.
@ -73,9 +74,11 @@ void AsyncFileStorage::store_impl(const QString &fileName, const QByteArray &dat
const QString filePath = m_storageDir.absoluteFilePath(fileName); const QString filePath = m_storageDir.absoluteFilePath(fileName);
QSaveFile file(filePath); QSaveFile file(filePath);
qDebug() << "AsyncFileStorage: Saving data to" << filePath; qDebug() << "AsyncFileStorage: Saving data to" << filePath;
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly))
{
file.write(data); file.write(data);
if (!file.commit()) { if (!file.commit())
{
qDebug() << "AsyncFileStorage: Failed to save data"; qDebug() << "AsyncFileStorage: Failed to save data";
emit failed(filePath, file.errorString()); emit failed(filePath, file.errorString());
} }

View file

@ -65,13 +65,16 @@ bool BandwidthScheduler::isTimeForAlternative() const
const int day = QDate::currentDate().dayOfWeek(); const int day = QDate::currentDate().dayOfWeek();
bool alternative = false; bool alternative = false;
if (start > end) { if (start > end)
{
std::swap(start, end); std::swap(start, end);
alternative = true; alternative = true;
} }
if ((start <= now) && (end >= now)) { if ((start <= now) && (end >= now))
switch (schedulerDays) { {
switch (schedulerDays)
{
case EVERY_DAY: case EVERY_DAY:
alternative = !alternative; alternative = !alternative;
break; break;
@ -96,7 +99,8 @@ void BandwidthScheduler::onTimeout()
{ {
const bool alternative = isTimeForAlternative(); const bool alternative = isTimeForAlternative();
if (alternative != m_lastAlternative) { if (alternative != m_lastAlternative)
{
m_lastAlternative = alternative; m_lastAlternative = alternative;
emit bandwidthLimitRequested(alternative); emit bandwidthLimitRequested(alternative);
} }

View file

@ -54,7 +54,8 @@ lt::storage_holder CustomDiskIOThread::new_torrent(const lt::storage_params &sto
lt::storage_holder storageHolder = m_nativeDiskIO->new_torrent(storageParams, torrent); lt::storage_holder storageHolder = m_nativeDiskIO->new_torrent(storageParams, torrent);
const QString savePath = Utils::Fs::expandPathAbs(QString::fromStdString(storageParams.path)); const QString savePath = Utils::Fs::expandPathAbs(QString::fromStdString(storageParams.path));
m_storageData[storageHolder] = { m_storageData[storageHolder] =
{
savePath savePath
, storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files , storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files
, storageParams.priorities}; , storageParams.priorities};
@ -196,7 +197,8 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
const QDir saveDir {savePath}; const QDir saveDir {savePath};
const StorageData storageData = m_storageData[storage]; const StorageData storageData = m_storageData[storage];
const lt::file_storage &fileStorage = storageData.files; const lt::file_storage &fileStorage = storageData.files;
for (const lt::file_index_t fileIndex : fileStorage.file_range()) { for (const lt::file_index_t fileIndex : fileStorage.file_range())
{
// ignore files that have priority 0 // ignore files that have priority 0
if ((storageData.filePriorities.end_index() > fileIndex) && (storageData.filePriorities[fileIndex] == lt::dont_download)) if ((storageData.filePriorities.end_index() > fileIndex) && (storageData.filePriorities[fileIndex] == lt::dont_download))
continue; continue;
@ -205,10 +207,12 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
if (fileStorage.pad_file_at(fileIndex)) continue; if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex)); const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex));
if (filePath.endsWith(QB_EXT)) { if (filePath.endsWith(QB_EXT))
{
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size()); const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size());
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)}; QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
if (completeFile.exists()) { if (completeFile.exists())
{
QFile incompleteFile {saveDir.absoluteFilePath(filePath)}; QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
incompleteFile.remove(); incompleteFile.remove();
completeFile.rename(incompleteFile.fileName()); completeFile.rename(incompleteFile.fileName());
@ -261,7 +265,8 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
const QDir saveDir {savePath}; const QDir saveDir {savePath};
const lt::file_storage &fileStorage = files(); const lt::file_storage &fileStorage = files();
for (const lt::file_index_t fileIndex : fileStorage.file_range()) { for (const lt::file_index_t fileIndex : fileStorage.file_range())
{
// ignore files that have priority 0 // ignore files that have priority 0
if ((m_filePriorities.end_index() > fileIndex) && (m_filePriorities[fileIndex] == lt::dont_download)) if ((m_filePriorities.end_index() > fileIndex) && (m_filePriorities[fileIndex] == lt::dont_download))
continue; continue;
@ -270,10 +275,12 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
if (fileStorage.pad_file_at(fileIndex)) continue; if (fileStorage.pad_file_at(fileIndex)) continue;
const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex)); const QString filePath = QString::fromStdString(fileStorage.file_path(fileIndex));
if (filePath.endsWith(QB_EXT)) { if (filePath.endsWith(QB_EXT))
{
const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size()); const QString completeFilePath = filePath.left(filePath.size() - QB_EXT.size());
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)}; QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
if (completeFile.exists()) { if (completeFile.exists())
{
QFile incompleteFile {saveDir.absoluteFilePath(filePath)}; QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
incompleteFile.remove(); incompleteFile.remove();
completeFile.rename(incompleteFile.fileName()); completeFile.rename(incompleteFile.fileName());

View file

@ -32,7 +32,8 @@ namespace BitTorrent
{ {
bool isValidDownloadPriority(const DownloadPriority priority) bool isValidDownloadPriority(const DownloadPriority priority)
{ {
switch (priority) { switch (priority)
{
case DownloadPriority::Ignored: case DownloadPriority::Ignored:
case DownloadPriority::Normal: case DownloadPriority::Normal:
case DownloadPriority::High: case DownloadPriority::High:

View file

@ -48,8 +48,10 @@ namespace
const char *octetStart = str; const char *octetStart = str;
char *endptr; char *endptr;
for (; *str; ++str) { for (; *str; ++str)
if (*str == '.') { {
if (*str == '.')
{
const long int extractedNum = strtol(octetStart, &endptr, 10); const long int extractedNum = strtol(octetStart, &endptr, 10);
if ((extractedNum >= 0L) && (extractedNum <= 255L)) if ((extractedNum >= 0L) && (extractedNum <= 255L))
m_buf[octetIndex++] = static_cast<unsigned char>(extractedNum); m_buf[octetIndex++] = static_cast<unsigned char>(extractedNum);
@ -65,7 +67,8 @@ namespace
} }
} }
if (str != octetStart) { if (str != octetStart)
{
const long int extractedNum = strtol(octetStart, &endptr, 10); const long int extractedNum = strtol(octetStart, &endptr, 10);
if ((extractedNum >= 0L) && (extractedNum <= 255L)) if ((extractedNum >= 0L) && (extractedNum <= 255L))
m_buf[octetIndex] = static_cast<unsigned char>(strtol(octetStart, &endptr, 10)); m_buf[octetIndex] = static_cast<unsigned char>(strtol(octetStart, &endptr, 10));
@ -124,7 +127,8 @@ int FilterParserThread::parseDATFilterFile()
QFile file(m_filePath); QFile file(m_filePath);
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL); LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -142,7 +146,8 @@ int FilterParserThread::parseDATFilterFile()
LogMsg(msg, Log::CRITICAL); LogMsg(msg, Log::CRITICAL);
}; };
while (true) { while (true)
{
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1); bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
if (bytesRead < 0) if (bytesRead < 0)
break; break;
@ -150,12 +155,16 @@ int FilterParserThread::parseDATFilterFile()
if ((bytesRead == 0) && (dataSize == 0)) if ((bytesRead == 0) && (dataSize == 0))
break; break;
for (start = 0; start < dataSize; ++start) { for (start = 0; start < dataSize; ++start)
{
endOfLine = -1; endOfLine = -1;
// The file might have ended without the last line having a newline // The file might have ended without the last line having a newline
if (!((bytesRead == 0) && (dataSize > 0))) { if (!((bytesRead == 0) && (dataSize > 0)))
for (int i = start; i < dataSize; ++i) { {
if (buffer[i] == '\n') { for (int i = start; i < dataSize; ++i)
{
if (buffer[i] == '\n')
{
endOfLine = i; endOfLine = i;
// We need to NULL the newline in case the line has only an IP range. // We need to NULL the newline in case the line has only an IP range.
// In that case the parser won't work for the end IP, because it ends // In that case the parser won't work for the end IP, because it ends
@ -165,12 +174,14 @@ int FilterParserThread::parseDATFilterFile()
} }
} }
} }
else { else
{
endOfLine = dataSize; endOfLine = dataSize;
buffer[dataSize] = '\0'; buffer[dataSize] = '\0';
} }
if (endOfLine == -1) { if (endOfLine == -1)
{
// read the next chunk from file // read the next chunk from file
// but first move(copy) the leftover data to the front of the buffer // but first move(copy) the leftover data to the front of the buffer
offset = dataSize - start; offset = dataSize - start;
@ -181,7 +192,8 @@ int FilterParserThread::parseDATFilterFile()
++nbLine; ++nbLine;
if ((buffer[start] == '#') if ((buffer[start] == '#')
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/')))) { || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
{
start = endOfLine; start = endOfLine;
continue; continue;
} }
@ -194,11 +206,13 @@ int FilterParserThread::parseDATFilterFile()
findAndNullDelimiter(buffer.data(), ',', firstComma + 1, endOfLine); findAndNullDelimiter(buffer.data(), ',', firstComma + 1, endOfLine);
// Check if there is an access value (apparently not mandatory) // Check if there is an access value (apparently not mandatory)
if (firstComma != -1) { if (firstComma != -1)
{
// There is possibly one // There is possibly one
const long int nbAccess = strtol(buffer.data() + firstComma + 1, nullptr, 10); const long int nbAccess = strtol(buffer.data() + firstComma + 1, nullptr, 10);
// Ignoring this rule because access value is too high // Ignoring this rule because access value is too high
if (nbAccess > 127L) { if (nbAccess > 127L)
{
start = endOfLine; start = endOfLine;
continue; continue;
} }
@ -207,7 +221,8 @@ int FilterParserThread::parseDATFilterFile()
// IP Range should be split by a dash // IP Range should be split by a dash
const int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1)); const int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
const int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange); const int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
if (delimIP == -1) { if (delimIP == -1)
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -216,7 +231,8 @@ int FilterParserThread::parseDATFilterFile()
lt::address startAddr; lt::address startAddr;
int newStart = trim(buffer.data(), start, delimIP - 1); int newStart = trim(buffer.data(), start, delimIP - 1);
if (!parseIPAddress(buffer.data() + newStart, startAddr)) { if (!parseIPAddress(buffer.data() + newStart, startAddr))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -225,7 +241,8 @@ int FilterParserThread::parseDATFilterFile()
lt::address endAddr; lt::address endAddr;
newStart = trim(buffer.data(), delimIP + 1, endOfIPRange); newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
if (!parseIPAddress(buffer.data() + newStart, endAddr)) { if (!parseIPAddress(buffer.data() + newStart, endAddr))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -233,7 +250,8 @@ int FilterParserThread::parseDATFilterFile()
} }
if ((startAddr.is_v4() != endAddr.is_v4()) if ((startAddr.is_v4() != endAddr.is_v4())
|| (startAddr.is_v6() != endAddr.is_v6())) { || (startAddr.is_v6() != endAddr.is_v6()))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -243,11 +261,13 @@ int FilterParserThread::parseDATFilterFile()
start = endOfLine; start = endOfLine;
// Now Add to the filter // Now Add to the filter
try { try
{
m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked); m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
++ruleCount; ++ruleCount;
} }
catch (const std::exception &e) { catch (const std::exception &e)
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter exception thrown for line %1. Exception is: %2") addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
.arg(nbLine).arg(QString::fromLocal8Bit(e.what()))); .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
@ -271,7 +291,8 @@ int FilterParserThread::parseP2PFilterFile()
QFile file(m_filePath); QFile file(m_filePath);
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL); LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -289,7 +310,8 @@ int FilterParserThread::parseP2PFilterFile()
LogMsg(msg, Log::CRITICAL); LogMsg(msg, Log::CRITICAL);
}; };
while (true) { while (true)
{
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1); bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
if (bytesRead < 0) if (bytesRead < 0)
break; break;
@ -297,12 +319,16 @@ int FilterParserThread::parseP2PFilterFile()
if ((bytesRead == 0) && (dataSize == 0)) if ((bytesRead == 0) && (dataSize == 0))
break; break;
for (start = 0; start < dataSize; ++start) { for (start = 0; start < dataSize; ++start)
{
endOfLine = -1; endOfLine = -1;
// The file might have ended without the last line having a newline // The file might have ended without the last line having a newline
if (!((bytesRead == 0) && (dataSize > 0))) { if (!((bytesRead == 0) && (dataSize > 0)))
for (int i = start; i < dataSize; ++i) { {
if (buffer[i] == '\n') { for (int i = start; i < dataSize; ++i)
{
if (buffer[i] == '\n')
{
endOfLine = i; endOfLine = i;
// We need to NULL the newline in case the line has only an IP range. // We need to NULL the newline in case the line has only an IP range.
// In that case the parser won't work for the end IP, because it ends // In that case the parser won't work for the end IP, because it ends
@ -312,12 +338,14 @@ int FilterParserThread::parseP2PFilterFile()
} }
} }
} }
else { else
{
endOfLine = dataSize; endOfLine = dataSize;
buffer[dataSize] = '\0'; buffer[dataSize] = '\0';
} }
if (endOfLine == -1) { if (endOfLine == -1)
{
// read the next chunk from file // read the next chunk from file
// but first move(copy) the leftover data to the front of the buffer // but first move(copy) the leftover data to the front of the buffer
offset = dataSize - start; offset = dataSize - start;
@ -328,7 +356,8 @@ int FilterParserThread::parseP2PFilterFile()
++nbLine; ++nbLine;
if ((buffer[start] == '#') if ((buffer[start] == '#')
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/')))) { || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
{
start = endOfLine; start = endOfLine;
continue; continue;
} }
@ -337,7 +366,8 @@ int FilterParserThread::parseP2PFilterFile()
// Some organization:1.0.0.0-1.255.255.255 // Some organization:1.0.0.0-1.255.255.255
// The "Some organization" part might contain a ':' char itself so we find the last occurrence // The "Some organization" part might contain a ':' char itself so we find the last occurrence
const int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true); const int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
if (partsDelimiter == -1) { if (partsDelimiter == -1)
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -346,7 +376,8 @@ int FilterParserThread::parseP2PFilterFile()
// IP Range should be split by a dash // IP Range should be split by a dash
const int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine); const int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
if (delimIP == -1) { if (delimIP == -1)
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -355,7 +386,8 @@ int FilterParserThread::parseP2PFilterFile()
lt::address startAddr; lt::address startAddr;
int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1); int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
if (!parseIPAddress(buffer.data() + newStart, startAddr)) { if (!parseIPAddress(buffer.data() + newStart, startAddr))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -364,7 +396,8 @@ int FilterParserThread::parseP2PFilterFile()
lt::address endAddr; lt::address endAddr;
newStart = trim(buffer.data(), delimIP + 1, endOfLine); newStart = trim(buffer.data(), delimIP + 1, endOfLine);
if (!parseIPAddress(buffer.data() + newStart, endAddr)) { if (!parseIPAddress(buffer.data() + newStart, endAddr))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -372,7 +405,8 @@ int FilterParserThread::parseP2PFilterFile()
} }
if ((startAddr.is_v4() != endAddr.is_v4()) if ((startAddr.is_v4() != endAddr.is_v4())
|| (startAddr.is_v6() != endAddr.is_v6())) { || (startAddr.is_v6() != endAddr.is_v6()))
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine)); addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
start = endOfLine; start = endOfLine;
@ -381,11 +415,13 @@ int FilterParserThread::parseP2PFilterFile()
start = endOfLine; start = endOfLine;
try { try
{
m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked); m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
++ruleCount; ++ruleCount;
} }
catch (const std::exception &e) { catch (const std::exception &e)
{
++parseErrorCount; ++parseErrorCount;
addLog(tr("IP filter exception thrown for line %1. Exception is: %2") addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
.arg(nbLine).arg(QString::fromLocal8Bit(e.what()))); .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
@ -407,14 +443,18 @@ int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name,
char c; char c;
int totalRead = 0; int totalRead = 0;
int read; int read;
do { do
{
read = stream.readRawData(&c, 1); read = stream.readRawData(&c, 1);
totalRead += read; totalRead += read;
if (read > 0) { if (read > 0)
if (c != delim) { {
if (c != delim)
{
name += c; name += c;
} }
else { else
{
// Delim found // Delim found
return totalRead; return totalRead;
} }
@ -432,7 +472,8 @@ int FilterParserThread::parseP2BFilterFile()
QFile file(m_filePath); QFile file(m_filePath);
if (!file.exists()) return ruleCount; if (!file.exists()) return ruleCount;
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly))
{
LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL); LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -443,19 +484,23 @@ int FilterParserThread::parseP2BFilterFile()
unsigned char version; unsigned char version;
if (!stream.readRawData(buf, sizeof(buf)) if (!stream.readRawData(buf, sizeof(buf))
|| memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7) || memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7)
|| !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version))) { || !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version)))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
if ((version == 1) || (version == 2)) { if ((version == 1) || (version == 2))
{
qDebug ("p2b version 1 or 2"); qDebug ("p2b version 1 or 2");
unsigned int start, end; unsigned int start, end;
std::string name; std::string name;
while (getlineInStream(stream, name, '\0') && !m_abort) { while (getlineInStream(stream, name, '\0') && !m_abort)
{
if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start)) if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
|| !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end))) { || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -466,26 +511,31 @@ int FilterParserThread::parseP2BFilterFile()
const lt::address_v4 first(ntohl(start)); const lt::address_v4 first(ntohl(start));
const lt::address_v4 last(ntohl(end)); const lt::address_v4 last(ntohl(end));
// Apply to bittorrent session // Apply to bittorrent session
try { try
{
m_filter.add_rule(first, last, lt::ip_filter::blocked); m_filter.add_rule(first, last, lt::ip_filter::blocked);
++ruleCount; ++ruleCount;
} }
catch (const std::exception &) {} catch (const std::exception &) {}
} }
} }
else if (version == 3) { else if (version == 3)
{
qDebug ("p2b version 3"); qDebug ("p2b version 3");
unsigned int namecount; unsigned int namecount;
if (!stream.readRawData(reinterpret_cast<char*>(&namecount), sizeof(namecount))) { if (!stream.readRawData(reinterpret_cast<char*>(&namecount), sizeof(namecount)))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
namecount = ntohl(namecount); namecount = ntohl(namecount);
// Reading names although, we don't really care about them // Reading names although, we don't really care about them
for (unsigned int i = 0; i < namecount; ++i) { for (unsigned int i = 0; i < namecount; ++i)
{
std::string name; std::string name;
if (!getlineInStream(stream, name, '\0')) { if (!getlineInStream(stream, name, '\0'))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -495,17 +545,20 @@ int FilterParserThread::parseP2BFilterFile()
// Reading the ranges // Reading the ranges
unsigned int rangecount; unsigned int rangecount;
if (!stream.readRawData(reinterpret_cast<char*>(&rangecount), sizeof(rangecount))) { if (!stream.readRawData(reinterpret_cast<char*>(&rangecount), sizeof(rangecount)))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
rangecount = ntohl(rangecount); rangecount = ntohl(rangecount);
unsigned int name, start, end; unsigned int name, start, end;
for (unsigned int i = 0; i < rangecount; ++i) { for (unsigned int i = 0; i < rangecount; ++i)
{
if (!stream.readRawData(reinterpret_cast<char*>(&name), sizeof(name)) if (!stream.readRawData(reinterpret_cast<char*>(&name), sizeof(name))
|| !stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start)) || !stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
|| !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end))) { || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount; return ruleCount;
} }
@ -516,7 +569,8 @@ int FilterParserThread::parseP2BFilterFile()
const lt::address_v4 first(ntohl(start)); const lt::address_v4 first(ntohl(start));
const lt::address_v4 last(ntohl(end)); const lt::address_v4 last(ntohl(end));
// Apply to bittorrent session // Apply to bittorrent session
try { try
{
m_filter.add_rule(first, last, lt::ip_filter::blocked); m_filter.add_rule(first, last, lt::ip_filter::blocked);
++ruleCount; ++ruleCount;
} }
@ -525,7 +579,8 @@ int FilterParserThread::parseP2BFilterFile()
if (m_abort) return ruleCount; if (m_abort) return ruleCount;
} }
} }
else { else
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL); LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
} }
@ -539,7 +594,8 @@ int FilterParserThread::parseP2BFilterFile()
// * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format // * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format
void FilterParserThread::processFilterFile(const QString &filePath) void FilterParserThread::processFilterFile(const QString &filePath)
{ {
if (isRunning()) { if (isRunning())
{
// Already parsing a filter, m_abort first // Already parsing a filter, m_abort first
m_abort = true; m_abort = true;
wait(); wait();
@ -561,25 +617,30 @@ void FilterParserThread::run()
{ {
qDebug("Processing filter file"); qDebug("Processing filter file");
int ruleCount = 0; int ruleCount = 0;
if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive)) { if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive))
{
// PeerGuardian p2p file // PeerGuardian p2p file
ruleCount = parseP2PFilterFile(); ruleCount = parseP2PFilterFile();
} }
else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive)) { else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive))
{
// PeerGuardian p2b file // PeerGuardian p2b file
ruleCount = parseP2BFilterFile(); ruleCount = parseP2BFilterFile();
} }
else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive)) { else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive))
{
// eMule DAT format // eMule DAT format
ruleCount = parseDATFilterFile(); ruleCount = parseDATFilterFile();
} }
if (m_abort) return; if (m_abort) return;
try { try
{
emit IPFilterParsed(ruleCount); emit IPFilterParsed(ruleCount);
} }
catch (const std::exception &) { catch (const std::exception &)
{
emit IPFilterError(); emit IPFilterError();
} }
@ -588,17 +649,23 @@ void FilterParserThread::run()
int FilterParserThread::findAndNullDelimiter(char *const data, const char delimiter, const int start, const int end, const bool reverse) int FilterParserThread::findAndNullDelimiter(char *const data, const char delimiter, const int start, const int end, const bool reverse)
{ {
if (!reverse) { if (!reverse)
for (int i = start; i <= end; ++i) { {
if (data[i] == delimiter) { for (int i = start; i <= end; ++i)
{
if (data[i] == delimiter)
{
data[i] = '\0'; data[i] = '\0';
return i; return i;
} }
} }
} }
else { else
for (int i = end; i >= start; --i) { {
if (data[i] == delimiter) { for (int i = end; i >= start; --i)
{
if (data[i] == delimiter)
{
data[i] = '\0'; data[i] = '\0';
return i; return i;
} }
@ -613,17 +680,21 @@ int FilterParserThread::trim(char *const data, const int start, const int end)
if (start >= end) return start; if (start >= end) return start;
int newStart = start; int newStart = start;
for (int i = start; i <= end; ++i) { for (int i = start; i <= end; ++i)
if (isspace(data[i]) != 0) { {
if (isspace(data[i]) != 0)
{
data[i] = '\0'; data[i] = '\0';
} }
else { else
{
newStart = i; newStart = i;
break; break;
} }
} }
for (int i = end; i >= start; --i) { for (int i = end; i >= start; --i)
{
if (isspace(data[i]) != 0) if (isspace(data[i]) != 0)
data[i] = '\0'; data[i] = '\0';
else else

View file

@ -36,7 +36,8 @@ namespace
{ {
void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert) void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert)
{ {
if (alert->error.value() == lt::errors::mismatching_file_size) { if (alert->error.value() == lt::errors::mismatching_file_size)
{
alert->handle.unset_flags(lt::torrent_flags::auto_managed); alert->handle.unset_flags(lt::torrent_flags::auto_managed);
alert->handle.pause(); alert->handle.pause();
} }
@ -55,7 +56,8 @@ std::shared_ptr<lt::torrent_plugin> NativeSessionExtension::new_torrent(const lt
void NativeSessionExtension::on_alert(const lt::alert *alert) void NativeSessionExtension::on_alert(const lt::alert *alert)
{ {
switch (alert->type()) { switch (alert->type())
{
case lt::fastresume_rejected_alert::alert_type: case lt::fastresume_rejected_alert::alert_type:
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert)); handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
break; break;

View file

@ -36,14 +36,17 @@ PeerAddress PeerAddress::parse(const QString &address)
{ {
QVector<QStringRef> ipPort; QVector<QStringRef> ipPort;
if (address.startsWith('[') && address.contains("]:")) { // IPv6 if (address.startsWith('[') && address.contains("]:"))
{ // IPv6
ipPort = address.splitRef("]:"); ipPort = address.splitRef("]:");
ipPort[0] = ipPort[0].mid(1); // chop '[' ipPort[0] = ipPort[0].mid(1); // chop '['
} }
else if (address.contains(':')) { // IPv4 else if (address.contains(':'))
{ // IPv4
ipPort = address.splitRef(':'); ipPort = address.splitRef(':');
} }
else { else
{
return {}; return {};
} }

View file

@ -208,7 +208,8 @@ qlonglong PeerInfo::totalDownload() const
QBitArray PeerInfo::pieces() const QBitArray PeerInfo::pieces() const
{ {
QBitArray result(m_nativeInfo.pieces.size()); QBitArray result(m_nativeInfo.pieces.size());
for (int i = 0; i < result.size(); ++i) { for (int i = 0; i < result.size(); ++i)
{
if (m_nativeInfo.pieces[lt::piece_index_t {i}]) if (m_nativeInfo.pieces[lt::piece_index_t {i}])
result.setBit(i, true); result.setBit(i, true);
} }
@ -233,8 +234,10 @@ void PeerInfo::calcRelevance(const TorrentHandle *torrent)
int localMissing = 0; int localMissing = 0;
int remoteHaves = 0; int remoteHaves = 0;
for (int i = 0; i < allPieces.size(); ++i) { for (int i = 0; i < allPieces.size(); ++i)
if (!allPieces[i]) { {
if (!allPieces[i])
{
++localMissing; ++localMissing;
if (peerPieces[i]) if (peerPieces[i])
++remoteHaves; ++remoteHaves;
@ -254,14 +257,17 @@ qreal PeerInfo::relevance() const
void PeerInfo::determineFlags() void PeerInfo::determineFlags()
{ {
if (isInteresting()) { if (isInteresting())
{
// d = Your client wants to download, but peer doesn't want to send (interested and choked) // d = Your client wants to download, but peer doesn't want to send (interested and choked)
if (isRemoteChocked()) { if (isRemoteChocked())
{
m_flags += "d "; m_flags += "d ";
m_flagsDescription += ("d = " m_flagsDescription += ("d = "
+ tr("Interested(local) and Choked(peer)") + '\n'); + tr("Interested(local) and Choked(peer)") + '\n');
} }
else { else
{
// D = Currently downloading (interested and not choked) // D = Currently downloading (interested and not choked)
m_flags += "D "; m_flags += "D ";
m_flagsDescription += ("D = " m_flagsDescription += ("D = "
@ -269,14 +275,17 @@ void PeerInfo::determineFlags()
} }
} }
if (isRemoteInterested()) { if (isRemoteInterested())
{
// u = Peer wants your client to upload, but your client doesn't want to (interested and choked) // u = Peer wants your client to upload, but your client doesn't want to (interested and choked)
if (isChocked()) { if (isChocked())
{
m_flags += "u "; m_flags += "u ";
m_flagsDescription += ("u = " m_flagsDescription += ("u = "
+ tr("interested(peer) and choked(local)") + '\n'); + tr("interested(peer) and choked(local)") + '\n');
} }
else { else
{
// U = Currently uploading (interested and not choked) // U = Currently uploading (interested and not choked)
m_flags += "U "; m_flags += "U ";
m_flagsDescription += ("U = " m_flagsDescription += ("U = "
@ -285,69 +294,80 @@ void PeerInfo::determineFlags()
} }
// O = Optimistic unchoke // O = Optimistic unchoke
if (optimisticUnchoke()) { if (optimisticUnchoke())
{
m_flags += "O "; m_flags += "O ";
m_flagsDescription += ("O = " + tr("optimistic unchoke") + '\n'); m_flagsDescription += ("O = " + tr("optimistic unchoke") + '\n');
} }
// S = Peer is snubbed // S = Peer is snubbed
if (isSnubbed()) { if (isSnubbed())
{
m_flags += "S "; m_flags += "S ";
m_flagsDescription += ("S = " + tr("peer snubbed") + '\n'); m_flagsDescription += ("S = " + tr("peer snubbed") + '\n');
} }
// I = Peer is an incoming connection // I = Peer is an incoming connection
if (!isLocalConnection()) { if (!isLocalConnection())
{
m_flags += "I "; m_flags += "I ";
m_flagsDescription += ("I = " + tr("incoming connection") + '\n'); m_flagsDescription += ("I = " + tr("incoming connection") + '\n');
} }
// K = Peer is unchoking your client, but your client is not interested // K = Peer is unchoking your client, but your client is not interested
if (!isRemoteChocked() && !isInteresting()) { if (!isRemoteChocked() && !isInteresting())
{
m_flags += "K "; m_flags += "K ";
m_flagsDescription += ("K = " m_flagsDescription += ("K = "
+ tr("not interested(local) and unchoked(peer)") + '\n'); + tr("not interested(local) and unchoked(peer)") + '\n');
} }
// ? = Your client unchoked the peer but the peer is not interested // ? = Your client unchoked the peer but the peer is not interested
if (!isChocked() && !isRemoteInterested()) { if (!isChocked() && !isRemoteInterested())
{
m_flags += "? "; m_flags += "? ";
m_flagsDescription += ("? = " m_flagsDescription += ("? = "
+ tr("not interested(peer) and unchoked(local)") + '\n'); + tr("not interested(peer) and unchoked(local)") + '\n');
} }
// X = Peer was included in peerlists obtained through Peer Exchange (PEX) // X = Peer was included in peerlists obtained through Peer Exchange (PEX)
if (fromPeX()) { if (fromPeX())
{
m_flags += "X "; m_flags += "X ";
m_flagsDescription += ("X = " + tr("peer from PEX") + '\n'); m_flagsDescription += ("X = " + tr("peer from PEX") + '\n');
} }
// H = Peer was obtained through DHT // H = Peer was obtained through DHT
if (fromDHT()) { if (fromDHT())
{
m_flags += "H "; m_flags += "H ";
m_flagsDescription += ("H = " + tr("peer from DHT") + '\n'); m_flagsDescription += ("H = " + tr("peer from DHT") + '\n');
} }
// E = Peer is using Protocol Encryption (all traffic) // E = Peer is using Protocol Encryption (all traffic)
if (isRC4Encrypted()) { if (isRC4Encrypted())
{
m_flags += "E "; m_flags += "E ";
m_flagsDescription += ("E = " + tr("encrypted traffic") + '\n'); m_flagsDescription += ("E = " + tr("encrypted traffic") + '\n');
} }
// e = Peer is using Protocol Encryption (handshake) // e = Peer is using Protocol Encryption (handshake)
if (isPlaintextEncrypted()) { if (isPlaintextEncrypted())
{
m_flags += "e "; m_flags += "e ";
m_flagsDescription += ("e = " + tr("encrypted handshake") + '\n'); m_flagsDescription += ("e = " + tr("encrypted handshake") + '\n');
} }
// P = Peer is using uTorrent uTP // P = Peer is using uTorrent uTP
if (useUTPSocket()) { if (useUTPSocket())
{
m_flags += "P "; m_flags += "P ";
m_flagsDescription += ("P = " + QString::fromUtf8(C_UTP) + '\n'); m_flagsDescription += ("P = " + QString::fromUtf8(C_UTP) + '\n');
} }
// L = Peer is local // L = Peer is local
if (fromLSD()) { if (fromLSD())
{
m_flags += "L "; m_flags += "L ";
m_flagsDescription += ("L = " + tr("peer from LSD") + '\n'); m_flagsDescription += ("L = " + tr("peer from LSD") + '\n');
} }

View file

@ -58,7 +58,8 @@ bool PortForwarderImpl::isEnabled() const
void PortForwarderImpl::setEnabled(const bool enabled) void PortForwarderImpl::setEnabled(const bool enabled)
{ {
if (m_active != enabled) { if (m_active != enabled)
{
if (enabled) if (enabled)
start(); start();
else else
@ -71,7 +72,8 @@ void PortForwarderImpl::setEnabled(const bool enabled)
void PortForwarderImpl::addPort(const quint16 port) void PortForwarderImpl::addPort(const quint16 port)
{ {
if (!m_mappedPorts.contains(port)) { if (!m_mappedPorts.contains(port))
{
m_mappedPorts.insert(port, {}); m_mappedPorts.insert(port, {});
if (isEnabled()) if (isEnabled())
m_mappedPorts[port] = {m_provider->add_port_mapping(lt::session::tcp, port, port)}; m_mappedPorts[port] = {m_provider->add_port_mapping(lt::session::tcp, port, port)};
@ -80,8 +82,10 @@ void PortForwarderImpl::addPort(const quint16 port)
void PortForwarderImpl::deletePort(const quint16 port) void PortForwarderImpl::deletePort(const quint16 port)
{ {
if (m_mappedPorts.contains(port)) { if (m_mappedPorts.contains(port))
if (isEnabled()) { {
if (isEnabled())
{
for (const lt::port_mapping_t &portMapping : m_mappedPorts[port]) for (const lt::port_mapping_t &portMapping : m_mappedPorts[port])
m_provider->delete_port_mapping(portMapping); m_provider->delete_port_mapping(portMapping);
} }
@ -96,7 +100,8 @@ void PortForwarderImpl::start()
settingsPack.set_bool(lt::settings_pack::enable_upnp, true); settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true); settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
m_provider->apply_settings(settingsPack); m_provider->apply_settings(settingsPack);
for (auto i = m_mappedPorts.begin(); i != m_mappedPorts.end(); ++i) { for (auto i = m_mappedPorts.begin(); i != m_mappedPorts.end(); ++i)
{
// quint16 port = i.key(); // quint16 port = i.key();
i.value() = {m_provider->add_port_mapping(lt::session::tcp, i.key(), i.key())}; i.value() = {m_provider->add_port_mapping(lt::session::tcp, i.key(), i.key())};
} }

View file

@ -48,7 +48,8 @@ void ResumeDataSavingManager::save(const QString &filename, const QByteArray &da
const QString filepath = m_resumeDataDir.absoluteFilePath(filename); const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
QSaveFile file {filepath}; QSaveFile file {filepath};
if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.commit()) { if (!file.open(QIODevice::WriteOnly) || (file.write(data) != data.size()) || !file.commit())
{
LogMsg(tr("Couldn't save data to '%1'. Error: %2") LogMsg(tr("Couldn't save data to '%1'. Error: %2")
.arg(filepath, file.errorString()), Log::CRITICAL); .arg(filepath, file.errorString()), Log::CRITICAL);
} }
@ -59,14 +60,16 @@ void ResumeDataSavingManager::save(const QString &filename, const std::shared_pt
const QString filepath = m_resumeDataDir.absoluteFilePath(filename); const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
QSaveFile file {filepath}; QSaveFile file {filepath};
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly))
{
LogMsg(tr("Couldn't save data to '%1'. Error: %2") LogMsg(tr("Couldn't save data to '%1'. Error: %2")
.arg(filepath, file.errorString()), Log::CRITICAL); .arg(filepath, file.errorString()), Log::CRITICAL);
return; return;
} }
lt::bencode(Utils::IO::FileDeviceOutputIterator {file}, *data); lt::bencode(Utils::IO::FileDeviceOutputIterator {file}, *data);
if ((file.error() != QFileDevice::NoError) || !file.commit()) { if ((file.error() != QFileDevice::NoError) || !file.commit())
{
LogMsg(tr("Couldn't save data to '%1'. Error: %2") LogMsg(tr("Couldn't save data to '%1'. Error: %2")
.arg(filepath, file.errorString()), Log::CRITICAL); .arg(filepath, file.errorString()), Log::CRITICAL);
} }

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,8 @@ SpeedMonitor::SpeedMonitor()
void SpeedMonitor::addSample(const SpeedSample &sample) void SpeedMonitor::addSample(const SpeedSample &sample)
{ {
if (m_speedSamples.size() >= MAX_SAMPLES) { if (m_speedSamples.size() >= MAX_SAMPLES)
{
m_sum -= m_speedSamples.front(); m_sum -= m_speedSamples.front();
} }

View file

@ -71,11 +71,13 @@ quint64 Statistics::getAlltimeUL() const
void Statistics::gather() void Statistics::gather()
{ {
const SessionStatus &ss = m_session->status(); const SessionStatus &ss = m_session->status();
if (ss.totalDownload > m_sessionDL) { if (ss.totalDownload > m_sessionDL)
{
m_sessionDL = ss.totalDownload; m_sessionDL = ss.totalDownload;
m_dirty = true; m_dirty = true;
} }
if (ss.totalUpload > m_sessionUL) { if (ss.totalUpload > m_sessionUL)
{
m_sessionUL = ss.totalUpload; m_sessionUL = ss.totalUpload;
m_dirty = true; m_dirty = true;
} }

View file

@ -59,7 +59,8 @@ namespace
#if (LIBTORRENT_VERSION_NUM >= 20000) #if (LIBTORRENT_VERSION_NUM >= 20000)
lt::create_flags_t toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat) lt::create_flags_t toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat)
{ {
switch (torrentFormat) { switch (torrentFormat)
{
case BitTorrent::TorrentFormat::V1: case BitTorrent::TorrentFormat::V1:
return lt::create_torrent::v1_only; return lt::create_torrent::v1_only;
case BitTorrent::TorrentFormat::Hybrid: case BitTorrent::TorrentFormat::Hybrid:
@ -102,20 +103,24 @@ void TorrentCreatorThread::run()
emit updateProgress(0); emit updateProgress(0);
try { try
{
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/'; const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
// Adding files to the torrent // Adding files to the torrent
lt::file_storage fs; lt::file_storage fs;
if (QFileInfo(m_params.inputPath).isFile()) { if (QFileInfo(m_params.inputPath).isFile())
{
lt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter); lt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
} }
else { else
{
// need to sort the file names by natural sort order // need to sort the file names by natural sort order
QStringList dirs = {m_params.inputPath}; QStringList dirs = {m_params.inputPath};
QDirIterator dirIter(m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories); QDirIterator dirIter(m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories);
while (dirIter.hasNext()) { while (dirIter.hasNext())
{
dirIter.next(); dirIter.next();
dirs += dirIter.filePath(); dirs += dirIter.filePath();
} }
@ -124,11 +129,13 @@ void TorrentCreatorThread::run()
QStringList fileNames; QStringList fileNames;
QHash<QString, qint64> fileSizeMap; QHash<QString, qint64> fileSizeMap;
for (const auto &dir : asConst(dirs)) { for (const auto &dir : asConst(dirs))
{
QStringList tmpNames; // natural sort files within each dir QStringList tmpNames; // natural sort files within each dir
QDirIterator fileIter(dir, QDir::Files); QDirIterator fileIter(dir, QDir::Files);
while (fileIter.hasNext()) { while (fileIter.hasNext())
{
fileIter.next(); fileIter.next();
const QString relFilePath = fileIter.filePath().mid(parentPath.length()); const QString relFilePath = fileIter.filePath().mid(parentPath.length());
@ -154,14 +161,16 @@ void TorrentCreatorThread::run()
#endif #endif
// Add url seeds // Add url seeds
for (QString seed : asConst(m_params.urlSeeds)) { for (QString seed : asConst(m_params.urlSeeds))
{
seed = seed.trimmed(); seed = seed.trimmed();
if (!seed.isEmpty()) if (!seed.isEmpty())
newTorrent.add_url_seed(seed.toStdString()); newTorrent.add_url_seed(seed.toStdString());
} }
int tier = 0; int tier = 0;
for (const QString &tracker : asConst(m_params.trackers)) { for (const QString &tracker : asConst(m_params.trackers))
{
if (tracker.isEmpty()) if (tracker.isEmpty())
++tier; ++tier;
else else
@ -195,16 +204,20 @@ void TorrentCreatorThread::run()
// create the torrent // create the torrent
QFile outfile {m_params.savePath}; QFile outfile {m_params.savePath};
if (!outfile.open(QIODevice::WriteOnly)) { if (!outfile.open(QIODevice::WriteOnly))
throw RuntimeError {tr("Create new torrent file failed. Reason: %1") {
throw RuntimeError
{tr("Create new torrent file failed. Reason: %1")
.arg(outfile.errorString())}; .arg(outfile.errorString())};
} }
if (isInterruptionRequested()) return; if (isInterruptionRequested()) return;
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry); lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
if (outfile.error() != QFileDevice::NoError) { if (outfile.error() != QFileDevice::NoError)
throw RuntimeError {tr("Create new torrent file failed. Reason: %1") {
throw RuntimeError
{tr("Create new torrent file failed. Reason: %1")
.arg(outfile.errorString())}; .arg(outfile.errorString())};
} }
outfile.close(); outfile.close();
@ -212,7 +225,8 @@ void TorrentCreatorThread::run()
emit updateProgress(100); emit updateProgress(100);
emit creationSuccess(m_params.savePath, parentPath); emit creationSuccess(m_params.savePath, parentPath);
} }
catch (const std::exception &e) { catch (const std::exception &e)
{
emit creationFailure(e.what()); emit creationFailure(e.what());
} }
} }

View file

@ -126,10 +126,12 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle
updateStatus(); updateStatus();
m_hash = InfoHash(m_nativeStatus.info_hash); m_hash = InfoHash(m_nativeStatus.info_hash);
if (hasMetadata()) { if (hasMetadata())
{
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority); applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
if (!params.restored) { if (!params.restored)
{
if (filesCount() == 1) if (filesCount() == 1)
m_hasRootFolder = false; m_hasRootFolder = false;
} }
@ -138,11 +140,13 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle
// TODO: Remove the following upgrade code in v.4.4 // TODO: Remove the following upgrade code in v.4.4
// == BEGIN UPGRADE CODE == // == BEGIN UPGRADE CODE ==
const QString spath = actualStorageLocation(); const QString spath = actualStorageLocation();
for (int i = 0; i < filesCount(); ++i) { for (int i = 0; i < filesCount(); ++i)
{
const QString filepath = filePath(i); const QString filepath = filePath(i);
// Move "unwanted" files back to their original folder // Move "unwanted" files back to their original folder
const QString parentRelPath = Utils::Fs::branchPath(filepath); const QString parentRelPath = Utils::Fs::branchPath(filepath);
if (QDir(parentRelPath).dirName() == ".unwanted") { if (QDir(parentRelPath).dirName() == ".unwanted")
{
const QString oldName = Utils::Fs::fileName(filepath); const QString oldName = Utils::Fs::fileName(filepath);
const QString newRelPath = Utils::Fs::branchPath(parentRelPath); const QString newRelPath = Utils::Fs::branchPath(parentRelPath);
if (newRelPath.isEmpty()) if (newRelPath.isEmpty())
@ -178,7 +182,8 @@ QString TorrentHandleImpl::name() const
name = QString::fromStdString(m_nativeStatus.name); name = QString::fromStdString(m_nativeStatus.name);
if (!name.isEmpty()) return name; if (!name.isEmpty()) return name;
if (hasMetadata()) { if (hasMetadata())
{
name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name()); name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name());
if (!name.isEmpty()) return name; if (!name.isEmpty()) return name;
} }
@ -330,8 +335,10 @@ void TorrentHandleImpl::addTrackers(const QVector<TrackerEntry> &trackers)
QVector<TrackerEntry> newTrackers; QVector<TrackerEntry> newTrackers;
newTrackers.reserve(trackers.size()); newTrackers.reserve(trackers.size());
for (const TrackerEntry &tracker : trackers) { for (const TrackerEntry &tracker : trackers)
if (!currentTrackers.contains(tracker)) { {
if (!currentTrackers.contains(tracker))
{
m_nativeHandle.add_tracker(tracker.nativeEntry()); m_nativeHandle.add_tracker(tracker.nativeEntry());
newTrackers << tracker; newTrackers << tracker;
} }
@ -351,7 +358,8 @@ void TorrentHandleImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
std::vector<lt::announce_entry> nativeTrackers; std::vector<lt::announce_entry> nativeTrackers;
nativeTrackers.reserve(trackers.size()); nativeTrackers.reserve(trackers.size());
for (const TrackerEntry &tracker : trackers) { for (const TrackerEntry &tracker : trackers)
{
nativeTrackers.emplace_back(tracker.nativeEntry()); nativeTrackers.emplace_back(tracker.nativeEntry());
if (!currentTrackers.removeOne(tracker)) if (!currentTrackers.removeOne(tracker))
@ -360,11 +368,13 @@ void TorrentHandleImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
m_nativeHandle.replace_trackers(nativeTrackers); m_nativeHandle.replace_trackers(nativeTrackers);
if (newTrackers.isEmpty() && currentTrackers.isEmpty()) { if (newTrackers.isEmpty() && currentTrackers.isEmpty())
{
// when existing tracker reorders // when existing tracker reorders
m_session->handleTorrentTrackersChanged(this); m_session->handleTorrentTrackersChanged(this);
} }
else { else
{
if (!currentTrackers.isEmpty()) if (!currentTrackers.isEmpty())
m_session->handleTorrentTrackersRemoved(this, currentTrackers); m_session->handleTorrentTrackersRemoved(this, currentTrackers);
@ -398,9 +408,11 @@ void TorrentHandleImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
QVector<QUrl> addedUrlSeeds; QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size()); addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds) { for (const QUrl &url : urlSeeds)
{
const std::string nativeUrl = url.toString().toStdString(); const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) == currentSeeds.end()) { if (currentSeeds.find(nativeUrl) == currentSeeds.end())
{
m_nativeHandle.add_url_seed(nativeUrl); m_nativeHandle.add_url_seed(nativeUrl);
addedUrlSeeds << url; addedUrlSeeds << url;
} }
@ -417,9 +429,11 @@ void TorrentHandleImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
QVector<QUrl> removedUrlSeeds; QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size()); removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds) { for (const QUrl &url : urlSeeds)
{
const std::string nativeUrl = url.toString().toStdString(); const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) != currentSeeds.end()) { if (currentSeeds.find(nativeUrl) != currentSeeds.end())
{
m_nativeHandle.remove_url_seed(nativeUrl); m_nativeHandle.remove_url_seed(nativeUrl);
removedUrlSeeds << url; removedUrlSeeds << url;
} }
@ -443,10 +457,12 @@ bool TorrentHandleImpl::connectPeer(const PeerAddress &peerAddress)
if (ec) return false; if (ec) return false;
const lt::tcp::endpoint endpoint(addr, peerAddress.port); const lt::tcp::endpoint endpoint(addr, peerAddress.port);
try { try
{
m_nativeHandle.connect_peer(endpoint); m_nativeHandle.connect_peer(endpoint);
} }
catch (const lt::system_error &err) { catch (const lt::system_error &err)
{
LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3") LogMsg(tr("Failed to add peer \"%1\" to torrent \"%2\". Reason: %3")
.arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING); .arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
return false; return false;
@ -533,7 +549,8 @@ bool TorrentHandleImpl::addTag(const QString &tag)
if (!Session::isValidTag(tag)) if (!Session::isValidTag(tag))
return false; return false;
if (!hasTag(tag)) { if (!hasTag(tag))
{
if (!m_session->hasTag(tag)) if (!m_session->hasTag(tag))
if (!m_session->addTag(tag)) if (!m_session->addTag(tag))
return false; return false;
@ -546,7 +563,8 @@ bool TorrentHandleImpl::addTag(const QString &tag)
bool TorrentHandleImpl::removeTag(const QString &tag) bool TorrentHandleImpl::removeTag(const QString &tag)
{ {
if (m_tags.remove(tag)) { if (m_tags.remove(tag))
{
m_session->handleTorrentTagRemoved(this, tag); m_session->handleTorrentTagRemoved(this, tag);
return true; return true;
} }
@ -719,25 +737,31 @@ TorrentState TorrentHandleImpl::state() const
void TorrentHandleImpl::updateState() void TorrentHandleImpl::updateState()
{ {
if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) { if (m_nativeStatus.state == lt::torrent_status::checking_resume_data)
{
m_state = TorrentState::CheckingResumeData; m_state = TorrentState::CheckingResumeData;
} }
else if (isMoveInProgress()) { else if (isMoveInProgress())
{
m_state = TorrentState::Moving; m_state = TorrentState::Moving;
} }
else if (hasMissingFiles()) { else if (hasMissingFiles())
{
m_state = TorrentState::MissingFiles; m_state = TorrentState::MissingFiles;
} }
else if (hasError()) { else if (hasError())
{
m_state = TorrentState::Error; m_state = TorrentState::Error;
} }
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) else if ((m_nativeStatus.state == lt::torrent_status::checking_files)
&& (!isPaused() || (m_nativeStatus.flags & lt::torrent_flags::auto_managed) && (!isPaused() || (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
|| !(m_nativeStatus.flags & lt::torrent_flags::paused))) { || !(m_nativeStatus.flags & lt::torrent_flags::paused)))
{
// If the torrent is not just in the "checking" state, but is being actually checked // If the torrent is not just in the "checking" state, but is being actually checked
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading; m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
} }
else if (isSeed()) { else if (isSeed())
{
if (isPaused()) if (isPaused())
m_state = TorrentState::PausedUploading; m_state = TorrentState::PausedUploading;
else if (m_session->isQueueingSystemEnabled() && isQueued()) else if (m_session->isQueueingSystemEnabled() && isQueued())
@ -749,7 +773,8 @@ void TorrentHandleImpl::updateState()
else else
m_state = TorrentState::StalledUploading; m_state = TorrentState::StalledUploading;
} }
else { else
{
if (isPaused()) if (isPaused())
m_state = TorrentState::PausedDownloading; m_state = TorrentState::PausedDownloading;
else if (m_nativeStatus.state == lt::torrent_status::downloading_metadata) else if (m_nativeStatus.state == lt::torrent_status::downloading_metadata)
@ -832,14 +857,16 @@ qlonglong TorrentHandleImpl::eta() const
const SpeedSampleAvg speedAverage = m_speedMonitor.average(); const SpeedSampleAvg speedAverage = m_speedMonitor.average();
if (isSeed()) { if (isSeed())
{
const qreal maxRatioValue = maxRatio(); const qreal maxRatioValue = maxRatio();
const int maxSeedingTimeValue = maxSeedingTime(); const int maxSeedingTimeValue = maxSeedingTime();
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA; if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
qlonglong ratioEta = MAX_ETA; qlonglong ratioEta = MAX_ETA;
if ((speedAverage.upload > 0) && (maxRatioValue >= 0)) { if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
{
qlonglong realDL = totalDownload(); qlonglong realDL = totalDownload();
if (realDL <= 0) if (realDL <= 0)
@ -850,7 +877,8 @@ qlonglong TorrentHandleImpl::eta() const
qlonglong seedingTimeEta = MAX_ETA; qlonglong seedingTimeEta = MAX_ETA;
if (maxSeedingTimeValue >= 0) { if (maxSeedingTimeValue >= 0)
{
seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime(); seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime();
if (seedingTimeEta < 0) if (seedingTimeEta < 0)
seedingTimeEta = 0; seedingTimeEta = 0;
@ -872,7 +900,8 @@ QVector<qreal> TorrentHandleImpl::filesProgress() const
const int count = static_cast<int>(fp.size()); const int count = static_cast<int>(fp.size());
QVector<qreal> result; QVector<qreal> result;
result.reserve(count); result.reserve(count);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i)
{
const qlonglong size = fileSize(i); const qlonglong size = fileSize(i);
if ((size <= 0) || (fp[i] == size)) if ((size <= 0) || (fp[i] == size))
result << 1; result << 1;
@ -995,7 +1024,8 @@ QVector<PeerInfo> TorrentHandleImpl::peers() const
QBitArray TorrentHandleImpl::pieces() const QBitArray TorrentHandleImpl::pieces() const
{ {
QBitArray result(m_nativeStatus.pieces.size()); QBitArray result(m_nativeStatus.pieces.size());
for (int i = 0; i < result.size(); ++i) { for (int i = 0; i < result.size(); ++i)
{
if (m_nativeStatus.pieces[lt::piece_index_t {i}]) if (m_nativeStatus.pieces[lt::piece_index_t {i}])
result.setBit(i, true); result.setBit(i, true);
} }
@ -1097,7 +1127,8 @@ qlonglong TorrentHandleImpl::nextAnnounce() const
void TorrentHandleImpl::setName(const QString &name) void TorrentHandleImpl::setName(const QString &name)
{ {
if (m_name != name) { if (m_name != name)
{
m_name = name; m_name = name;
m_session->handleTorrentNameChanged(this); m_session->handleTorrentNameChanged(this);
} }
@ -1105,7 +1136,8 @@ void TorrentHandleImpl::setName(const QString &name)
bool TorrentHandleImpl::setCategory(const QString &category) bool TorrentHandleImpl::setCategory(const QString &category)
{ {
if (m_category != category) { if (m_category != category)
{
if (!category.isEmpty() && !m_session->categories().contains(category)) if (!category.isEmpty() && !m_session->categories().contains(category))
return false; return false;
@ -1113,7 +1145,8 @@ bool TorrentHandleImpl::setCategory(const QString &category)
m_category = category; m_category = category;
m_session->handleTorrentCategoryChanged(this, oldCategory); m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM) { if (m_useAutoTMM)
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged()) if (!m_session->isDisableAutoTMMWhenCategoryChanged())
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite); move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
else else
@ -1126,7 +1159,8 @@ bool TorrentHandleImpl::setCategory(const QString &category)
void TorrentHandleImpl::move(QString path) void TorrentHandleImpl::move(QString path)
{ {
if (m_useAutoTMM) { if (m_useAutoTMM)
{
m_useAutoTMM = false; m_useAutoTMM = false;
m_session->handleTorrentSavingModeChanged(this); m_session->handleTorrentSavingModeChanged(this);
} }
@ -1145,10 +1179,12 @@ void TorrentHandleImpl::move_impl(QString path, const MoveStorageMode mode)
if (path == savePath()) return; if (path == savePath()) return;
path = Utils::Fs::toNativePath(path); path = Utils::Fs::toNativePath(path);
if (!useTempPath()) { if (!useTempPath())
{
moveStorage(path, mode); moveStorage(path, mode);
} }
else { else
{
m_savePath = path; m_savePath = path;
m_session->handleTorrentSavePathChanged(this); m_session->handleTorrentSavePathChanged(this);
} }
@ -1172,7 +1208,8 @@ void TorrentHandleImpl::forceRecheck()
m_hasMissingFiles = false; m_hasMissingFiles = false;
m_unchecked = false; m_unchecked = false;
if (isPaused()) { if (isPaused())
{
// When "force recheck" is applied on paused torrent, we temporarily resume it // When "force recheck" is applied on paused torrent, we temporarily resume it
// (really we just allow libtorrent to resume it by enabling auto management for it). // (really we just allow libtorrent to resume it by enabling auto management for it).
m_nativeHandle.set_flags(lt::torrent_flags::stop_when_ready | lt::torrent_flags::auto_managed); m_nativeHandle.set_flags(lt::torrent_flags::stop_when_ready | lt::torrent_flags::auto_managed);
@ -1181,11 +1218,13 @@ void TorrentHandleImpl::forceRecheck()
void TorrentHandleImpl::setSequentialDownload(const bool enable) void TorrentHandleImpl::setSequentialDownload(const bool enable)
{ {
if (enable) { if (enable)
{
m_nativeHandle.set_flags(lt::torrent_flags::sequential_download); m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
} }
else { else
{
m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download); m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
} }
@ -1220,7 +1259,8 @@ void TorrentHandleImpl::applyFirstLastPiecePriority(const bool enabled, const QV
// Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
// we might get the old/wrong values, so we rely on `updatedFilePrio` in this case. // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index) { for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index)
{
const lt::download_priority_t filePrio = filePriorities[index]; const lt::download_priority_t filePrio = filePriorities[index];
if (filePrio <= lt::download_priority_t {0}) if (filePrio <= lt::download_priority_t {0})
continue; continue;
@ -1231,7 +1271,8 @@ void TorrentHandleImpl::applyFirstLastPiecePriority(const bool enabled, const QV
// worst case: AVI index = 1% of total file size (at the end of the file) // worst case: AVI index = 1% of total file size (at the end of the file)
const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength()); const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
for (int i = 0; i < nNumPieces; ++i) { for (int i = 0; i < nNumPieces; ++i)
{
piecePriorities[extremities.first() + i] = newPrio; piecePriorities[extremities.first() + i] = newPrio;
piecePriorities[extremities.last() - i] = newPrio; piecePriorities[extremities.last() - i] = newPrio;
} }
@ -1247,7 +1288,8 @@ void TorrentHandleImpl::pause()
m_speedMonitor.reset(); m_speedMonitor.reset();
if (!m_isStopped) { if (!m_isStopped)
{
m_isStopped = true; m_isStopped = true;
m_session->handleTorrentPaused(this); m_session->handleTorrentPaused(this);
} }
@ -1258,12 +1300,14 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode)
if (hasError()) if (hasError())
m_nativeHandle.clear_error(); m_nativeHandle.clear_error();
if (m_hasMissingFiles) { if (m_hasMissingFiles)
{
m_hasMissingFiles = false; m_hasMissingFiles = false;
m_nativeHandle.force_recheck(); m_nativeHandle.force_recheck();
} }
if (m_isStopped) { if (m_isStopped)
{
// Torrent may have been temporarily resumed to perform checking files // Torrent may have been temporarily resumed to perform checking files
// so we have to ensure it will not pause after checking is done. // so we have to ensure it will not pause after checking is done.
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready); m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
@ -1275,7 +1319,8 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode)
m_operatingMode = mode; m_operatingMode = mode;
if (m_isStopped) { if (m_isStopped)
{
m_isStopped = false; m_isStopped = false;
m_session->handleTorrentResumed(this); m_session->handleTorrentResumed(this);
} }
@ -1283,7 +1328,8 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode)
void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMode mode) void TorrentHandleImpl::moveStorage(const QString &newPath, const MoveStorageMode mode)
{ {
if (m_session->addMoveTorrentStorageJob(this, newPath, mode)) { if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
{
m_storageIsMoving = true; m_storageIsMoving = true;
updateStatus(); updateStatus();
} }
@ -1307,7 +1353,8 @@ void TorrentHandleImpl::handleMoveStorageJobFinished(const bool hasOutstandingJo
updateStatus(); updateStatus();
const QString newPath = QString::fromStdString(m_nativeStatus.save_path); const QString newPath = QString::fromStdString(m_nativeStatus.save_path);
if (!useTempPath() && (newPath != m_savePath)) { if (!useTempPath() && (newPath != m_savePath))
{
m_savePath = newPath; m_savePath = newPath;
m_session->handleTorrentSavePathChanged(this); m_session->handleTorrentSavePathChanged(this);
} }
@ -1363,14 +1410,16 @@ void TorrentHandleImpl::handleTorrentCheckedAlert(const lt::torrent_checked_aler
Q_UNUSED(p); Q_UNUSED(p);
qDebug("\"%s\" have just finished checking", qUtf8Printable(name())); qDebug("\"%s\" have just finished checking", qUtf8Printable(name()));
if (m_fastresumeDataRejected && !m_hasMissingFiles) { if (m_fastresumeDataRejected && !m_hasMissingFiles)
{
saveResumeData(); saveResumeData();
m_fastresumeDataRejected = false; m_fastresumeDataRejected = false;
} }
updateStatus(); updateStatus();
if (!m_hasMissingFiles) { if (!m_hasMissingFiles)
{
if ((progress() < 1.0) && (wantedSize() > 0)) if ((progress() < 1.0) && (wantedSize() > 0))
m_hasSeedStatus = false; m_hasSeedStatus = false;
else if (progress() == 1.0) else if (progress() == 1.0)
@ -1398,12 +1447,14 @@ void TorrentHandleImpl::handleTorrentFinishedAlert(const lt::torrent_finished_al
manageIncompleteFiles(); manageIncompleteFiles();
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion(); const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
if (isMoveInProgress() || (m_renameCount > 0)) { if (isMoveInProgress() || (m_renameCount > 0))
{
if (recheckTorrentsOnCompletion) if (recheckTorrentsOnCompletion)
m_moveFinishedTriggers.append([this]() { forceRecheck(); }); m_moveFinishedTriggers.append([this]() { forceRecheck(); });
m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); }); m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); });
} }
else { else
{
if (recheckTorrentsOnCompletion && m_unchecked) if (recheckTorrentsOnCompletion && m_unchecked)
forceRecheck(); forceRecheck();
m_session->handleTorrentFinished(this); m_session->handleTorrentFinished(this);
@ -1422,18 +1473,22 @@ void TorrentHandleImpl::handleTorrentResumedAlert(const lt::torrent_resumed_aler
void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
{ {
if (p && !m_hasMissingFiles) { if (p && !m_hasMissingFiles)
{
// Update recent resume data // Update recent resume data
m_ltAddTorrentParams = p->params; m_ltAddTorrentParams = p->params;
} }
if (!m_isStopped) { if (!m_isStopped)
{
// Torrent can be actually "running" but temporarily "paused" to perform some // Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running" // service jobs behind the scenes so we need to restore it as "running"
if (m_operatingMode == TorrentOperatingMode::AutoManaged) { if (m_operatingMode == TorrentOperatingMode::AutoManaged)
{
m_ltAddTorrentParams.flags |= lt::torrent_flags::auto_managed; m_ltAddTorrentParams.flags |= lt::torrent_flags::auto_managed;
} }
else { else
{
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused; m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused;
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed; m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
} }
@ -1449,7 +1504,8 @@ void TorrentHandleImpl::handleSaveResumeDataAlert(const lt::save_resume_data_ale
// TODO: The following code is deprecated. Remove after several releases in 4.3.x. // TODO: The following code is deprecated. Remove after several releases in 4.3.x.
// === BEGIN DEPRECATED CODE === // // === BEGIN DEPRECATED CODE === //
const bool useDummyResumeData = !p; const bool useDummyResumeData = !p;
if (useDummyResumeData) { if (useDummyResumeData)
{
updateStatus(); updateStatus();
resumeData["qBt-magnetUri"] = createMagnetURI().toStdString(); resumeData["qBt-magnetUri"] = createMagnetURI().toStdString();
@ -1489,12 +1545,14 @@ void TorrentHandleImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejec
{ {
m_fastresumeDataRejected = true; m_fastresumeDataRejected = true;
if (p->error.value() == lt::errors::mismatching_file_size) { if (p->error.value() == lt::errors::mismatching_file_size)
{
// Mismatching file size (files were probably moved) // Mismatching file size (files were probably moved)
m_hasMissingFiles = true; m_hasMissingFiles = true;
LogMsg(tr("File sizes mismatch for torrent '%1'. Cannot proceed further.").arg(name()), Log::CRITICAL); LogMsg(tr("File sizes mismatch for torrent '%1'. Cannot proceed further.").arg(name()), Log::CRITICAL);
} }
else { else
{
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...") LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
.arg(name(), QString::fromStdString(p->message())), Log::WARNING); .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
} }
@ -1527,13 +1585,15 @@ void TorrentHandleImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
#endif #endif
int pathIdx = 0; int pathIdx = 0;
while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) { while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size()))
{
if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0) if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
break; break;
++pathIdx; ++pathIdx;
} }
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) { for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i)
{
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/"))); QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
oldPathParts.removeLast(); oldPathParts.removeLast();
} }
@ -1571,9 +1631,11 @@ void TorrentHandleImpl::handleFileCompletedAlert(const lt::file_completed_alert
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name())); qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
if (m_session->isAppendExtensionEnabled()) { if (m_session->isAppendExtensionEnabled())
{
QString name = filePath(static_cast<LTUnderlyingType<lt::file_index_t>>(p->index)); QString name = filePath(static_cast<LTUnderlyingType<lt::file_index_t>>(p->index));
if (name.endsWith(QB_EXT)) { if (name.endsWith(QB_EXT))
{
const QString oldName = name; const QString oldName = name;
name.chop(QB_EXT.size()); name.chop(QB_EXT.size());
qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name)); qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name));
@ -1627,7 +1689,8 @@ void TorrentHandleImpl::handleAppendExtensionToggled()
void TorrentHandleImpl::handleAlert(const lt::alert *a) void TorrentHandleImpl::handleAlert(const lt::alert *a)
{ {
switch (a->type()) { switch (a->type())
{
case lt::file_renamed_alert::alert_type: case lt::file_renamed_alert::alert_type:
handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a)); handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
break; break;
@ -1680,22 +1743,28 @@ void TorrentHandleImpl::manageIncompleteFiles()
{ {
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled(); const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
const QVector<qreal> fp = filesProgress(); const QVector<qreal> fp = filesProgress();
if (fp.size() != filesCount()) { if (fp.size() != filesCount())
{
qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress"; qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
return; return;
} }
for (int i = 0; i < filesCount(); ++i) { for (int i = 0; i < filesCount(); ++i)
{
QString name = filePath(i); QString name = filePath(i);
if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) { if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1))
if (!name.endsWith(QB_EXT)) { {
if (!name.endsWith(QB_EXT))
{
const QString newName = name + QB_EXT; const QString newName = name + QB_EXT;
qDebug() << "Renaming" << name << "to" << newName; qDebug() << "Renaming" << name << "to" << newName;
renameFile(i, newName); renameFile(i, newName);
} }
} }
else { else
if (name.endsWith(QB_EXT)) { {
if (name.endsWith(QB_EXT))
{
const QString oldName = name; const QString oldName = name;
name.chop(QB_EXT.size()); name.chop(QB_EXT.size());
qDebug() << "Renaming" << oldName << "to" << name; qDebug() << "Renaming" << oldName << "to" << name;
@ -1722,8 +1791,10 @@ void TorrentHandleImpl::adjustActualSavePath_impl()
if (targetDir == currentDir) return; if (targetDir == currentDir) return;
if (!needUseTempDir) { if (!needUseTempDir)
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()})) { {
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()}))
{
// torrent without root folder still has it in its temporary save path // torrent without root folder still has it in its temporary save path
// so its temp path isn't equal to temp path root // so its temp path isn't equal to temp path root
const QString currentDirPath = currentDir.absolutePath(); const QString currentDirPath = currentDir.absolutePath();
@ -1790,7 +1861,8 @@ void TorrentHandleImpl::setRatioLimit(qreal limit)
else if (limit > MAX_RATIO) else if (limit > MAX_RATIO)
limit = MAX_RATIO; limit = MAX_RATIO;
if (m_ratioLimit != limit) { if (m_ratioLimit != limit)
{
m_ratioLimit = limit; m_ratioLimit = limit;
m_session->handleTorrentShareLimitChanged(this); m_session->handleTorrentShareLimitChanged(this);
} }
@ -1803,7 +1875,8 @@ void TorrentHandleImpl::setSeedingTimeLimit(int limit)
else if (limit > MAX_SEEDING_TIME) else if (limit > MAX_SEEDING_TIME)
limit = MAX_SEEDING_TIME; limit = MAX_SEEDING_TIME;
if (m_seedingTimeLimit != limit) { if (m_seedingTimeLimit != limit)
{
m_seedingTimeLimit = limit; m_seedingTimeLimit = limit;
m_session->handleTorrentShareLimitChanged(this); m_session->handleTorrentShareLimitChanged(this);
} }
@ -1846,10 +1919,12 @@ void TorrentHandleImpl::prioritizeFiles(const QVector<DownloadPriority> &priorit
// 'torrent_finished_alert' and eg show tray notifications // 'torrent_finished_alert' and eg show tray notifications
const QVector<qreal> progress = filesProgress(); const QVector<qreal> progress = filesProgress();
const QVector<DownloadPriority> oldPriorities = filePriorities(); const QVector<DownloadPriority> oldPriorities = filePriorities();
for (int i = 0; i < oldPriorities.size(); ++i) { for (int i = 0; i < oldPriorities.size(); ++i)
{
if ((oldPriorities[i] == DownloadPriority::Ignored) if ((oldPriorities[i] == DownloadPriority::Ignored)
&& (priorities[i] > DownloadPriority::Ignored) && (priorities[i] > DownloadPriority::Ignored)
&& (progress[i] < 1.0)) { && (progress[i] < 1.0))
{
m_hasSeedStatus = false; m_hasSeedStatus = false;
break; break;
} }
@ -1875,7 +1950,8 @@ QVector<qreal> TorrentHandleImpl::availableFileFractions() const
QVector<qreal> res; QVector<qreal> res;
res.reserve(filesCount); res.reserve(filesCount);
const TorrentInfo info = this->info(); const TorrentInfo info = this->info();
for (int i = 0; i < filesCount; ++i) { for (int i = 0; i < filesCount; ++i)
{
const TorrentInfo::PieceRange filePieces = info.filePieces(i); const TorrentInfo::PieceRange filePieces = info.filePieces(i);
int availablePieces = 0; int availablePieces = 0;

View file

@ -77,14 +77,16 @@ TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
lt::error_code ec; lt::error_code ec;
const lt::bdecode_node node = lt::bdecode(data, ec const lt::bdecode_node node = lt::bdecode(data, ec
, nullptr, depthLimit, tokenLimit); , nullptr, depthLimit, tokenLimit);
if (ec) { if (ec)
{
if (error) if (error)
*error = QString::fromStdString(ec.message()); *error = QString::fromStdString(ec.message());
return TorrentInfo(); return TorrentInfo();
} }
TorrentInfo info {std::shared_ptr<lt::torrent_info>(new lt::torrent_info(node, ec))}; TorrentInfo info {std::shared_ptr<lt::torrent_info>(new lt::torrent_info(node, ec))};
if (ec) { if (ec)
{
if (error) if (error)
*error = QString::fromStdString(ec.message()); *error = QString::fromStdString(ec.message());
return TorrentInfo(); return TorrentInfo();
@ -99,28 +101,33 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
error->clear(); error->clear();
QFile file {path}; QFile file {path};
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly))
{
if (error) if (error)
*error = file.errorString(); *error = file.errorString();
return TorrentInfo(); return TorrentInfo();
} }
if (file.size() > MAX_TORRENT_SIZE) { if (file.size() > MAX_TORRENT_SIZE)
{
if (error) if (error)
*error = tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE)); *error = tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE));
return TorrentInfo(); return TorrentInfo();
} }
QByteArray data; QByteArray data;
try { try
{
data = file.readAll(); data = file.readAll();
} }
catch (const std::bad_alloc &e) { catch (const std::bad_alloc &e)
{
if (error) if (error)
*error = tr("Torrent file read error: %1").arg(e.what()); *error = tr("Torrent file read error: %1").arg(e.what());
return TorrentInfo(); return TorrentInfo();
} }
if (data.size() != file.size()) { if (data.size() != file.size())
{
if (error) if (error)
*error = tr("Torrent file read error: size mismatch"); *error = tr("Torrent file read error: size mismatch");
return TorrentInfo(); return TorrentInfo();
@ -284,7 +291,8 @@ QVector<QUrl> TorrentInfo::urlSeeds() const
QVector<QUrl> urlSeeds; QVector<QUrl> urlSeeds;
urlSeeds.reserve(nativeWebSeeds.size()); urlSeeds.reserve(nativeWebSeeds.size());
for (const lt::web_seed_entry &webSeed : nativeWebSeeds) { for (const lt::web_seed_entry &webSeed : nativeWebSeeds)
{
if (webSeed.type == lt::web_seed_entry::url_seed) if (webSeed.type == lt::web_seed_entry::url_seed)
urlSeeds.append(QUrl(webSeed.url.c_str())); urlSeeds.append(QUrl(webSeed.url.c_str()));
} }
@ -353,7 +361,8 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const QString &file) const
return {}; return {};
const int index = fileIndex(file); const int index = fileIndex(file);
if (index == -1) { if (index == -1)
{
qDebug() << "Filename" << file << "was not found in torrent" << name(); qDebug() << "Filename" << file << "was not found in torrent" << name();
return {}; return {};
} }
@ -365,7 +374,8 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
if (!isValid()) if (!isValid())
return {}; return {};
if ((fileIndex < 0) || (fileIndex >= filesCount())) { if ((fileIndex < 0) || (fileIndex >= filesCount()))
{
qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name(); qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name();
return {}; return {};
} }
@ -402,7 +412,8 @@ int TorrentInfo::fileIndex(const QString &fileName) const
QString TorrentInfo::rootFolder() const QString TorrentInfo::rootFolder() const
{ {
QString rootFolder; QString rootFolder;
for (int i = 0; i < filesCount(); ++i) { for (int i = 0; i < filesCount(); ++i)
{
const QString filePath = this->filePath(i); const QString filePath = this->filePath(i);
if (QDir::isAbsolutePath(filePath)) continue; if (QDir::isAbsolutePath(filePath)) continue;
@ -433,7 +444,8 @@ void TorrentInfo::stripRootFolder()
// Solution for case of renamed root folder // Solution for case of renamed root folder
const QString path = filePath(0); const QString path = filePath(0);
const std::string newName = path.left(path.indexOf('/')).toStdString(); const std::string newName = path.left(path.indexOf('/')).toStdString();
if (files.name() != newName) { if (files.name() != newName)
{
files.set_name(newName); files.set_name(newName);
for (int i = 0; i < files.num_files(); ++i) for (int i = 0; i < files.num_files(); ++i)
files.rename_file(lt::file_index_t {i}, files.file_path(lt::file_index_t {i})); files.rename_file(lt::file_index_t {i}, files.file_path(lt::file_index_t {i}));

View file

@ -92,9 +92,11 @@ namespace
QByteArray toBigEndianByteArray(const QHostAddress &addr) QByteArray toBigEndianByteArray(const QHostAddress &addr)
{ {
// translate IP address to a sequence of bytes in big-endian order // translate IP address to a sequence of bytes in big-endian order
switch (addr.protocol()) { switch (addr.protocol())
{
case QAbstractSocket::IPv4Protocol: case QAbstractSocket::IPv4Protocol:
case QAbstractSocket::AnyIPProtocol: { case QAbstractSocket::AnyIPProtocol:
{
const quint32 ipv4 = addr.toIPv4Address(); const quint32 ipv4 = addr.toIPv4Address();
QByteArray ret; QByteArray ret;
ret.append(static_cast<char>((ipv4 >> 24) & 0xFF)) ret.append(static_cast<char>((ipv4 >> 24) & 0xFF))
@ -104,7 +106,8 @@ namespace
return ret; return ret;
} }
case QAbstractSocket::IPv6Protocol: { case QAbstractSocket::IPv6Protocol:
{
const Q_IPV6ADDR ipv6 = addr.toIPv6Address(); const Q_IPV6ADDR ipv6 = addr.toIPv6Address();
QByteArray ret; QByteArray ret;
for (const quint8 i : ipv6.c) for (const quint8 i : ipv6.c)
@ -162,7 +165,8 @@ struct Tracker::TrackerAnnounceRequest
void Tracker::TorrentStats::setPeer(const Peer &peer) void Tracker::TorrentStats::setPeer(const Peer &peer)
{ {
// always replace existing peer // always replace existing peer
if (!removePeer(peer)) { if (!removePeer(peer))
{
// Too many peers, remove a random one // Too many peers, remove a random one
if (peers.size() >= MAX_PEERS_PER_TORRENT) if (peers.size() >= MAX_PEERS_PER_TORRENT)
removePeer(*peers.begin()); removePeer(*peers.begin());
@ -198,8 +202,10 @@ bool Tracker::start()
const QHostAddress ip = QHostAddress::Any; const QHostAddress ip = QHostAddress::Any;
const int port = Preferences::instance()->getTrackerPort(); const int port = Preferences::instance()->getTrackerPort();
if (m_server->isListening()) { if (m_server->isListening())
if (m_server->serverPort() == port) { {
if (m_server->serverPort() == port)
{
// Already listening on the right port, just return // Already listening on the right port, just return
return true; return true;
} }
@ -211,11 +217,13 @@ bool Tracker::start()
// Listen on the predefined port // Listen on the predefined port
const bool listenSuccess = m_server->listen(ip, port); const bool listenSuccess = m_server->listen(ip, port);
if (listenSuccess) { if (listenSuccess)
{
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2") LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
.arg(ip.toString(), QString::number(port)), Log::INFO); .arg(ip.toString(), QString::number(port)), Log::INFO);
} }
else { else
{
LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3") LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
.arg(ip.toString(), QString::number(port), m_server->errorString()) .arg(ip.toString(), QString::number(port), m_server->errorString())
, Log::WARNING); , Log::WARNING);
@ -233,7 +241,8 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
status(200); status(200);
try { try
{
// Is it a GET request? // Is it a GET request?
if (request.method != Http::HEADER_REQUEST_METHOD_GET) if (request.method != Http::HEADER_REQUEST_METHOD_GET)
throw MethodNotAllowedHTTPError(); throw MethodNotAllowedHTTPError();
@ -243,16 +252,19 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
else else
throw NotFoundHTTPError(); throw NotFoundHTTPError();
} }
catch (const HTTPError &error) { catch (const HTTPError &error)
{
status(error.statusCode(), error.statusText()); status(error.statusCode(), error.statusText());
if (!error.message().isEmpty()) if (!error.message().isEmpty())
print(error.message(), Http::CONTENT_TYPE_TXT); print(error.message(), Http::CONTENT_TYPE_TXT);
} }
catch (const TrackerError &error) { catch (const TrackerError &error)
{
clear(); // clear response clear(); // clear response
status(200); status(200);
const lt::entry::dictionary_type bencodedEntry = { const lt::entry::dictionary_type bencodedEntry =
{
{ANNOUNCE_RESPONSE_FAILURE_REASON, {error.what()}} {ANNOUNCE_RESPONSE_FAILURE_REASON, {error.what()}}
}; };
QByteArray reply; QByteArray reply;
@ -312,7 +324,8 @@ void Tracker::processAnnounceRequest()
// 4. numwant // 4. numwant
const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT); const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT);
if (numWantIter != queryParams.end()) { if (numWantIter != queryParams.end())
{
const int num = numWantIter->toInt(); const int num = numWantIter->toInt();
if (num < 0) if (num < 0)
throw TrackerError("Invalid \"numwant\" parameter"); throw TrackerError("Invalid \"numwant\" parameter");
@ -348,15 +361,18 @@ void Tracker::processAnnounceRequest()
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_EMPTY) || (announceReq.event == ANNOUNCE_REQUEST_EVENT_EMPTY)
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_COMPLETED) || (announceReq.event == ANNOUNCE_REQUEST_EVENT_COMPLETED)
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_STARTED) || (announceReq.event == ANNOUNCE_REQUEST_EVENT_STARTED)
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED)) { || (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED))
{
// [BEP-21] Extension for partial seeds // [BEP-21] Extension for partial seeds
// (partial support - we don't support BEP-48 so the part that concerns that is not supported) // (partial support - we don't support BEP-48 so the part that concerns that is not supported)
registerPeer(announceReq); registerPeer(announceReq);
} }
else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED) { else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED)
{
unregisterPeer(announceReq); unregisterPeer(announceReq);
} }
else { else
{
throw TrackerError("Invalid \"event\" parameter"); throw TrackerError("Invalid \"event\" parameter");
} }
@ -365,7 +381,8 @@ void Tracker::processAnnounceRequest()
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq) void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{ {
if (!m_torrents.contains(announceReq.infoHash)) { if (!m_torrents.contains(announceReq.infoHash))
{
// Reached max size, remove a random torrent // Reached max size, remove a random torrent
if (m_torrents.size() >= MAX_TORRENTS) if (m_torrents.size() >= MAX_TORRENTS)
m_torrents.erase(m_torrents.begin()); m_torrents.erase(m_torrents.begin());
@ -390,7 +407,8 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
{ {
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash]; const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
lt::entry::dictionary_type replyDict { lt::entry::dictionary_type replyDict
{
{ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL}, {ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL},
{ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders}, {ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders},
{ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)}, {ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)},
@ -402,13 +420,16 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
// peer list // peer list
// [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23) // [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23)
// [BEP-23] Tracker Returns Compact Peer Lists // [BEP-23] Tracker Returns Compact Peer Lists
if (announceReq.compact) { if (announceReq.compact)
{
lt::entry::string_type peers; lt::entry::string_type peers;
lt::entry::string_type peers6; lt::entry::string_type peers6;
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED) { if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
{
int counter = 0; int counter = 0;
for (const Peer &peer : asConst(torrentStats.peers)) { for (const Peer &peer : asConst(torrentStats.peers))
{
if (counter++ >= announceReq.numwant) if (counter++ >= announceReq.numwant)
break; break;
@ -423,16 +444,20 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
if (!peers6.empty()) if (!peers6.empty())
replyDict[ANNOUNCE_RESPONSE_PEERS6] = peers6; replyDict[ANNOUNCE_RESPONSE_PEERS6] = peers6;
} }
else { else
{
lt::entry::list_type peerList; lt::entry::list_type peerList;
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED) { if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
{
int counter = 0; int counter = 0;
for (const Peer &peer : torrentStats.peers) { for (const Peer &peer : torrentStats.peers)
{
if (counter++ >= announceReq.numwant) if (counter++ >= announceReq.numwant)
break; break;
lt::entry::dictionary_type peerDict = { lt::entry::dictionary_type peerDict =
{
{ANNOUNCE_RESPONSE_PEERS_IP, peer.address}, {ANNOUNCE_RESPONSE_PEERS_IP, peer.address},
{ANNOUNCE_RESPONSE_PEERS_PORT, peer.port} {ANNOUNCE_RESPONSE_PEERS_PORT, peer.port}
}; };

View file

@ -107,7 +107,8 @@ void TrackerEntry::setTier(const int value)
int TrackerEntry::numSeeds() const int TrackerEntry::numSeeds() const
{ {
int value = -1; int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) { for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000) #if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes) for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_complete); value = std::max(value, infoHash.scrape_complete);
@ -121,7 +122,8 @@ int TrackerEntry::numSeeds() const
int TrackerEntry::numLeeches() const int TrackerEntry::numLeeches() const
{ {
int value = -1; int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) { for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000) #if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes) for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_incomplete); value = std::max(value, infoHash.scrape_incomplete);
@ -135,7 +137,8 @@ int TrackerEntry::numLeeches() const
int TrackerEntry::numDownloaded() const int TrackerEntry::numDownloaded() const
{ {
int value = -1; int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) { for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000) #if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes) for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_downloaded); value = std::max(value, infoHash.scrape_downloaded);

View file

@ -76,7 +76,8 @@ void FileSystemWatcher::addPath(const QString &path)
if (!dir.exists()) return; if (!dir.exists()) return;
// Check if the path points to a network file system or not // Check if the path points to a network file system or not
if (Utils::Fs::isNetworkFileSystem(path)) { if (Utils::Fs::isNetworkFileSystem(path))
{
// Network mode // Network mode
LogMsg(tr("Watching remote folder: \"%1\"").arg(Utils::Fs::toNativePath(path))); LogMsg(tr("Watching remote folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
m_watchedFolders << dir; m_watchedFolders << dir;
@ -94,7 +95,8 @@ void FileSystemWatcher::addPath(const QString &path)
void FileSystemWatcher::removePath(const QString &path) void FileSystemWatcher::removePath(const QString &path)
{ {
if (m_watchedFolders.removeOne(path)) { if (m_watchedFolders.removeOne(path))
{
if (m_watchedFolders.isEmpty()) if (m_watchedFolders.isEmpty())
m_watchTimer.stop(); m_watchTimer.stop();
return; return;
@ -125,12 +127,14 @@ void FileSystemWatcher::processPartialTorrents()
if (!QFile::exists(torrentPath)) if (!QFile::exists(torrentPath))
return true; return true;
if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) { if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid())
{
noLongerPartial << torrentPath; noLongerPartial << torrentPath;
return true; return true;
} }
if (value >= MAX_PARTIAL_RETRIES) { if (value >= MAX_PARTIAL_RETRIES)
{
QFile::rename(torrentPath, torrentPath + ".qbt_rejected"); QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
return true; return true;
} }
@ -140,11 +144,13 @@ void FileSystemWatcher::processPartialTorrents()
}); });
// Stop the partial timer if necessary // Stop the partial timer if necessary
if (m_partialTorrents.empty()) { if (m_partialTorrents.empty())
{
m_partialTorrentTimer.stop(); m_partialTorrentTimer.stop();
qDebug("No longer any partial torrent."); qDebug("No longer any partial torrent.");
} }
else { else
{
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count()); qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
m_partialTorrentTimer.start(WATCH_INTERVAL); m_partialTorrentTimer.start(WATCH_INTERVAL);
} }
@ -158,7 +164,8 @@ void FileSystemWatcher::processTorrentsInDir(const QDir &dir)
{ {
QStringList torrents; QStringList torrents;
const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files); const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files);
for (const QString &file : files) { for (const QString &file : files)
{
const QString fileAbsPath = dir.absoluteFilePath(file); const QString fileAbsPath = dir.absoluteFilePath(file);
if (file.endsWith(".magnet", Qt::CaseInsensitive)) if (file.endsWith(".magnet", Qt::CaseInsensitive))
torrents << fileAbsPath; torrents << fileAbsPath;

View file

@ -59,13 +59,17 @@ void Connection::read()
m_idleTimer.restart(); m_idleTimer.restart();
m_receivedData.append(m_socket->readAll()); m_receivedData.append(m_socket->readAll());
while (!m_receivedData.isEmpty()) { while (!m_receivedData.isEmpty())
{
const RequestParser::ParseResult result = RequestParser::parse(m_receivedData); const RequestParser::ParseResult result = RequestParser::parse(m_receivedData);
switch (result.status) { switch (result.status)
case RequestParser::ParseStatus::Incomplete: { {
case RequestParser::ParseStatus::Incomplete:
{
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
if (m_receivedData.size() > bufferLimit) { if (m_receivedData.size() > bufferLimit)
{
Logger::instance()->addMessage(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2") Logger::instance()->addMessage(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2")
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING); .arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
@ -78,7 +82,8 @@ void Connection::read()
} }
return; return;
case RequestParser::ParseStatus::BadRequest: { case RequestParser::ParseStatus::BadRequest:
{
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1") Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString()), Log::WARNING); .arg(m_socket->peerAddress().toString()), Log::WARNING);
@ -90,7 +95,8 @@ void Connection::read()
} }
return; return;
case RequestParser::ParseStatus::OK: { case RequestParser::ParseStatus::OK:
{
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()}; const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
Response resp = m_requestHandler->processRequest(result.request, env); Response resp = m_requestHandler->processRequest(result.request, env);
@ -133,7 +139,8 @@ bool Connection::acceptsGzipEncoding(QString codings)
const auto isCodingAvailable = [](const QVector<QStringRef> &list, const QString &encoding) -> bool const auto isCodingAvailable = [](const QVector<QStringRef> &list, const QString &encoding) -> bool
{ {
for (const QStringRef &str : list) { for (const QStringRef &str : list)
{
if (!str.startsWith(encoding)) if (!str.startsWith(encoding))
continue; continue;

View file

@ -61,7 +61,8 @@ namespace
{ {
// [rfc7230] 3.2. Header Fields // [rfc7230] 3.2. Header Fields
const int i = line.indexOf(':'); const int i = line.indexOf(':');
if (i <= 0) { if (i <= 0)
{
qWarning() << Q_FUNC_INFO << "invalid http header:" << line; qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
return false; return false;
} }
@ -88,13 +89,15 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
{ {
// we don't handle malformed requests which use double `LF` as delimiter // we don't handle malformed requests which use double `LF` as delimiter
const int headerEnd = data.indexOf(EOH); const int headerEnd = data.indexOf(EOH);
if (headerEnd < 0) { if (headerEnd < 0)
{
qDebug() << Q_FUNC_INFO << "incomplete request"; qDebug() << Q_FUNC_INFO << "incomplete request";
return {ParseStatus::Incomplete, Request(), 0}; return {ParseStatus::Incomplete, Request(), 0};
} }
const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd); const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
if (!parseStartLines(httpHeaders)) { if (!parseStartLines(httpHeaders))
{
qWarning() << Q_FUNC_INFO << "header parsing error"; qWarning() << Q_FUNC_INFO << "header parsing error";
return {ParseStatus::BadRequest, Request(), 0}; return {ParseStatus::BadRequest, Request(), 0};
} }
@ -104,26 +107,32 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
// handle supported methods // handle supported methods
if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD)) if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD))
return {ParseStatus::OK, m_request, headerLength}; return {ParseStatus::OK, m_request, headerLength};
if (m_request.method == HEADER_REQUEST_METHOD_POST) { if (m_request.method == HEADER_REQUEST_METHOD_POST)
{
bool ok = false; bool ok = false;
const int contentLength = m_request.headers[HEADER_CONTENT_LENGTH].toInt(&ok); const int contentLength = m_request.headers[HEADER_CONTENT_LENGTH].toInt(&ok);
if (!ok || (contentLength < 0)) { if (!ok || (contentLength < 0))
{
qWarning() << Q_FUNC_INFO << "bad request: content-length invalid"; qWarning() << Q_FUNC_INFO << "bad request: content-length invalid";
return {ParseStatus::BadRequest, Request(), 0}; return {ParseStatus::BadRequest, Request(), 0};
} }
if (contentLength > MAX_CONTENT_SIZE) { if (contentLength > MAX_CONTENT_SIZE)
{
qWarning() << Q_FUNC_INFO << "bad request: message too long"; qWarning() << Q_FUNC_INFO << "bad request: message too long";
return {ParseStatus::BadRequest, Request(), 0}; return {ParseStatus::BadRequest, Request(), 0};
} }
if (contentLength > 0) { if (contentLength > 0)
{
const QByteArray httpBodyView = midView(data, headerLength, contentLength); const QByteArray httpBodyView = midView(data, headerLength, contentLength);
if (httpBodyView.length() < contentLength) { if (httpBodyView.length() < contentLength)
{
qDebug() << Q_FUNC_INFO << "incomplete request"; qDebug() << Q_FUNC_INFO << "incomplete request";
return {ParseStatus::Incomplete, Request(), 0}; return {ParseStatus::Incomplete, Request(), 0};
} }
if (!parsePostMessage(httpBodyView)) { if (!parsePostMessage(httpBodyView))
{
qWarning() << Q_FUNC_INFO << "message body parsing error"; qWarning() << Q_FUNC_INFO << "message body parsing error";
return {ParseStatus::BadRequest, Request(), 0}; return {ParseStatus::BadRequest, Request(), 0};
} }
@ -143,12 +152,15 @@ bool RequestParser::parseStartLines(const QString &data)
// [rfc7230] 3.2.2. Field Order // [rfc7230] 3.2.2. Field Order
QStringList requestLines; QStringList requestLines;
for (const auto &line : lines) { for (const auto &line : lines)
if (line.at(0).isSpace() && !requestLines.isEmpty()) { {
if (line.at(0).isSpace() && !requestLines.isEmpty())
{
// continuation of previous line // continuation of previous line
requestLines.last() += line.toString(); requestLines.last() += line.toString();
} }
else { else
{
requestLines += line.toString(); requestLines += line.toString();
} }
} }
@ -159,7 +171,8 @@ bool RequestParser::parseStartLines(const QString &data)
if (!parseRequestLine(requestLines[0])) if (!parseRequestLine(requestLines[0]))
return false; return false;
for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i) { for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i)
{
if (!parseHeaderLine(*i, m_request.headers)) if (!parseHeaderLine(*i, m_request.headers))
return false; return false;
} }
@ -174,7 +187,8 @@ bool RequestParser::parseRequestLine(const QString &line)
const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$")); const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"));
const QRegularExpressionMatch match = re.match(line); const QRegularExpressionMatch match = re.match(line);
if (!match.hasMatch()) { if (!match.hasMatch())
{
qWarning() << Q_FUNC_INFO << "invalid http header:" << line; qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
return false; return false;
} }
@ -189,12 +203,14 @@ bool RequestParser::parseRequestLine(const QString &line)
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent)); m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent));
if (sepPos >= 0) { if (sepPos >= 0)
{
const QByteArray query = midView(url, (sepPos + 1)); const QByteArray query = midView(url, (sepPos + 1));
// [rfc3986] 2.4 When to Encode or Decode // [rfc3986] 2.4 When to Encode or Decode
// URL components should be separated before percent-decoding // URL components should be separated before percent-decoding
for (const QByteArray &param : asConst(splitToViews(query, "&"))) { for (const QByteArray &param : asConst(splitToViews(query, "&")))
{
const int eqCharPos = param.indexOf('='); const int eqCharPos = param.indexOf('=');
if (eqCharPos <= 0) continue; // ignores params without name if (eqCharPos <= 0) continue; // ignores params without name
@ -222,12 +238,14 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
const QString contentTypeLower = contentType.toLower(); const QString contentTypeLower = contentType.toLower();
// application/x-www-form-urlencoded // application/x-www-form-urlencoded
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) { if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED))
{
// [URL Standard] 5.1 application/x-www-form-urlencoded parsing // [URL Standard] 5.1 application/x-www-form-urlencoded parsing
const QByteArray processedData = QByteArray(data).replace('+', ' '); const QByteArray processedData = QByteArray(data).replace('+', ' ');
QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded)); QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded));
while (i.hasNext()) { while (i.hasNext())
{
const QStringPair pair = i.next(); const QStringPair pair = i.next();
m_request.posts[pair.first] = pair.second; m_request.posts[pair.first] = pair.second;
} }
@ -236,19 +254,22 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
} }
// multipart/form-data // multipart/form-data
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA)) { if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA))
{
// [rfc2046] 5.1.1. Common Syntax // [rfc2046] 5.1.1. Common Syntax
// find boundary delimiter // find boundary delimiter
const QLatin1String boundaryFieldName("boundary="); const QLatin1String boundaryFieldName("boundary=");
const int idx = contentType.indexOf(boundaryFieldName); const int idx = contentType.indexOf(boundaryFieldName);
if (idx < 0) { if (idx < 0)
{
qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!"; qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!";
return false; return false;
} }
const QByteArray delimiter = Utils::String::unquote(contentType.midRef(idx + boundaryFieldName.size())).toLatin1(); const QByteArray delimiter = Utils::String::unquote(contentType.midRef(idx + boundaryFieldName.size())).toLatin1();
if (delimiter.isEmpty()) { if (delimiter.isEmpty())
{
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!"; qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
return false; return false;
} }
@ -256,7 +277,8 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
// split data by "dash-boundary" // split data by "dash-boundary"
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF; const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
QVector<QByteArray> multipart = splitToViews(data, dashDelimiter, QString::SkipEmptyParts); QVector<QByteArray> multipart = splitToViews(data, dashDelimiter, QString::SkipEmptyParts);
if (multipart.isEmpty()) { if (multipart.isEmpty())
{
qWarning() << Q_FUNC_INFO << "multipart empty"; qWarning() << Q_FUNC_INFO << "multipart empty";
return false; return false;
} }
@ -279,7 +301,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
{ {
const QVector<QByteArray> list = splitToViews(data, EOH, QString::KeepEmptyParts); const QVector<QByteArray> list = splitToViews(data, EOH, QString::KeepEmptyParts);
if (list.size() != 2) { if (list.size() != 2)
{
qWarning() << Q_FUNC_INFO << "multipart/form-data format error"; qWarning() << Q_FUNC_INFO << "multipart/form-data format error";
return false; return false;
} }
@ -289,12 +312,15 @@ bool RequestParser::parseFormData(const QByteArray &data)
HeaderMap headersMap; HeaderMap headersMap;
const QVector<QStringRef> headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts); const QVector<QStringRef> headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts);
for (const auto &line : headerLines) { for (const auto &line : headerLines)
if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive)) { {
if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive))
{
// extract out filename & name // extract out filename & name
const QVector<QStringRef> directives = line.split(';', QString::SkipEmptyParts); const QVector<QStringRef> directives = line.split(';', QString::SkipEmptyParts);
for (const auto &directive : directives) { for (const auto &directive : directives)
{
const int idx = directive.indexOf('='); const int idx = directive.indexOf('=');
if (idx < 0) if (idx < 0)
continue; continue;
@ -304,7 +330,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
headersMap[name] = value; headersMap[name] = value;
} }
} }
else { else
{
if (!parseHeaderLine(line.toString(), headersMap)) if (!parseHeaderLine(line.toString(), headersMap))
return false; return false;
} }
@ -314,13 +341,16 @@ bool RequestParser::parseFormData(const QByteArray &data)
const QLatin1String filename("filename"); const QLatin1String filename("filename");
const QLatin1String name("name"); const QLatin1String name("name");
if (headersMap.contains(filename)) { if (headersMap.contains(filename))
{
m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload}); m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload});
} }
else if (headersMap.contains(name)) { else if (headersMap.contains(name))
{
m_request.posts[headersMap[name]] = payload; m_request.posts[headersMap[name]] = payload;
} }
else { else
{
// malformed // malformed
qWarning() << Q_FUNC_INFO << "multipart/form-data header error"; qWarning() << Q_FUNC_INFO << "multipart/form-data header error";
return false; return false;

View file

@ -93,12 +93,14 @@ void Server::incomingConnection(const qintptr socketDescriptor)
else else
serverSocket = new QTcpSocket(this); serverSocket = new QTcpSocket(this);
if (!serverSocket->setSocketDescriptor(socketDescriptor)) { if (!serverSocket->setSocketDescriptor(socketDescriptor))
{
delete serverSocket; delete serverSocket;
return; return;
} }
if (m_https) { if (m_https)
{
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols); static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key); static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates); static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
@ -134,7 +136,8 @@ bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privat
const QList<QSslCertificate> certs {Utils::Net::loadSSLCertificate(certificates)}; const QList<QSslCertificate> certs {Utils::Net::loadSSLCertificate(certificates)};
const QSslKey key {Utils::Net::loadSSLKey(privateKey)}; const QSslKey key {Utils::Net::loadSSLKey(privateKey)};
if (certs.isEmpty() || key.isNull()) { if (certs.isEmpty() || key.isNull())
{
disableHttps(); disableHttps();
return false; return false;
} }

View file

@ -56,7 +56,8 @@ DNSUpdater::DNSUpdater(QObject *parent)
// Check lastUpdate to avoid flooding // Check lastUpdate to avoid flooding
if (!m_lastIPCheckTime.isValid() if (!m_lastIPCheckTime.isValid()
|| (m_lastIPCheckTime.secsTo(QDateTime::currentDateTime()) * 1000 > IP_CHECK_INTERVAL_MS)) { || (m_lastIPCheckTime.secsTo(QDateTime::currentDateTime()) * 1000 > IP_CHECK_INTERVAL_MS))
{
checkPublicIP(); checkPublicIP();
} }
} }
@ -82,30 +83,36 @@ void DNSUpdater::checkPublicIP()
void DNSUpdater::ipRequestFinished(const DownloadResult &result) void DNSUpdater::ipRequestFinished(const DownloadResult &result)
{ {
if (result.status != DownloadStatus::Success) { if (result.status != DownloadStatus::Success)
{
qWarning() << "IP request failed:" << result.errorString; qWarning() << "IP request failed:" << result.errorString;
return; return;
} }
// Parse response // Parse response
const QRegularExpressionMatch ipRegexMatch = QRegularExpression("Current IP Address:\\s+([^<]+)</body>").match(result.data); const QRegularExpressionMatch ipRegexMatch = QRegularExpression("Current IP Address:\\s+([^<]+)</body>").match(result.data);
if (ipRegexMatch.hasMatch()) { if (ipRegexMatch.hasMatch())
{
QString ipStr = ipRegexMatch.captured(1); QString ipStr = ipRegexMatch.captured(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr; qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
QHostAddress newIp(ipStr); QHostAddress newIp(ipStr);
if (!newIp.isNull()) { if (!newIp.isNull())
if (m_lastIP != newIp) { {
if (m_lastIP != newIp)
{
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS..."; qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
qDebug() << m_lastIP.toString() << "->" << newIp.toString(); qDebug() << m_lastIP.toString() << "->" << newIp.toString();
m_lastIP = newIp; m_lastIP = newIp;
updateDNSService(); updateDNSService();
} }
} }
else { else
{
qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string"; qWarning() << Q_FUNC_INFO << "Failed to construct a QHostAddress from the IP string";
} }
} }
else { else
{
qWarning() << Q_FUNC_INFO << "Regular expression failed to capture the IP address"; qWarning() << Q_FUNC_INFO << "Regular expression failed to capture the IP address";
} }
} }
@ -133,7 +140,8 @@ QString DNSUpdater::getUpdateUrl() const
Q_ASSERT(!m_lastIP.isNull()); Q_ASSERT(!m_lastIP.isNull());
// Service specific // Service specific
switch (m_service) { switch (m_service)
{
case DNS::DYNDNS: case DNS::DYNDNS:
url.setHost("members.dyndns.org"); url.setHost("members.dyndns.org");
break; break;
@ -171,12 +179,14 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
const QString code = reply.split(' ').first(); const QString code = reply.split(' ').first();
qDebug() << Q_FUNC_INFO << "Code:" << code; qDebug() << Q_FUNC_INFO << "Code:" << code;
if ((code == "good") || (code == "nochg")) { if ((code == "good") || (code == "nochg"))
{
logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO); logger->addMessage(tr("Your dynamic DNS was successfully updated."), Log::INFO);
return; return;
} }
if ((code == "911") || (code == "dnserr")) { if ((code == "911") || (code == "dnserr"))
{
logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: The service is temporarily unavailable, it will be retried in 30 minutes."), Log::CRITICAL);
m_lastIP.clear(); m_lastIP.clear();
// It will retry in 30 minutes because the timer was not stopped // It will retry in 30 minutes because the timer was not stopped
@ -186,33 +196,38 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
// Everything below is an error, stop updating until the user updates something // Everything below is an error, stop updating until the user updates something
m_ipCheckTimer.stop(); m_ipCheckTimer.stop();
m_lastIP.clear(); m_lastIP.clear();
if (code == "nohost") { if (code == "nohost")
{
logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: hostname supplied does not exist under specified account."), Log::CRITICAL);
m_state = INVALID_CREDS; m_state = INVALID_CREDS;
return; return;
} }
if (code == "badauth") { if (code == "badauth")
{
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
m_state = INVALID_CREDS; m_state = INVALID_CREDS;
return; return;
} }
if (code == "badagent") { if (code == "badagent")
{
logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."), logger->addMessage(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please report a bug at http://bugs.qbittorrent.org."),
Log::CRITICAL); Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
return; return;
} }
if (code == "!donator") { if (code == "!donator")
{
logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"), logger->addMessage(tr("Dynamic DNS error: %1 was returned by the service, please report a bug at http://bugs.qbittorrent.org.").arg("!donator"),
Log::CRITICAL); Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
return; return;
} }
if (code == "abuse") { if (code == "abuse")
{
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
} }
@ -225,14 +240,17 @@ void DNSUpdater::updateCredentials()
Logger *const logger = Logger::instance(); Logger *const logger = Logger::instance();
bool change = false; bool change = false;
// Get DNS service information // Get DNS service information
if (m_service != pref->getDynDNSService()) { if (m_service != pref->getDynDNSService())
{
m_service = pref->getDynDNSService(); m_service = pref->getDynDNSService();
change = true; change = true;
} }
if (m_domain != pref->getDynDomainName()) { if (m_domain != pref->getDynDomainName())
{
m_domain = pref->getDynDomainName(); m_domain = pref->getDynDomainName();
const QRegularExpressionMatch domainRegexMatch = QRegularExpression("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$").match(m_domain); const QRegularExpressionMatch domainRegexMatch = QRegularExpression("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$").match(m_domain);
if (!domainRegexMatch.hasMatch()) { if (!domainRegexMatch.hasMatch())
{
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
m_lastIP.clear(); m_lastIP.clear();
m_ipCheckTimer.stop(); m_ipCheckTimer.stop();
@ -241,9 +259,11 @@ void DNSUpdater::updateCredentials()
} }
change = true; change = true;
} }
if (m_username != pref->getDynDNSUsername()) { if (m_username != pref->getDynDNSUsername())
{
m_username = pref->getDynDNSUsername(); m_username = pref->getDynDNSUsername();
if (m_username.length() < 4) { if (m_username.length() < 4)
{
logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: supplied username is too short."), Log::CRITICAL);
m_lastIP.clear(); m_lastIP.clear();
m_ipCheckTimer.stop(); m_ipCheckTimer.stop();
@ -252,9 +272,11 @@ void DNSUpdater::updateCredentials()
} }
change = true; change = true;
} }
if (m_password != pref->getDynDNSPassword()) { if (m_password != pref->getDynDNSPassword())
{
m_password = pref->getDynDNSPassword(); m_password = pref->getDynDNSPassword();
if (m_password.length() < 4) { if (m_password.length() < 4)
{
logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL); logger->addMessage(tr("Dynamic DNS error: supplied password is too short."), Log::CRITICAL);
m_lastIP.clear(); m_lastIP.clear();
m_ipCheckTimer.stop(); m_ipCheckTimer.stop();
@ -264,7 +286,8 @@ void DNSUpdater::updateCredentials()
change = true; change = true;
} }
if ((m_state == INVALID_CREDS) && change) { if ((m_state == INVALID_CREDS) && change)
{
m_state = OK; // Try again m_state = OK; // Try again
m_ipCheckTimer.start(); m_ipCheckTimer.start();
checkPublicIP(); checkPublicIP();
@ -273,7 +296,8 @@ void DNSUpdater::updateCredentials()
QUrl DNSUpdater::getRegistrationUrl(const int service) QUrl DNSUpdater::getRegistrationUrl(const int service)
{ {
switch (service) { switch (service)
{
case DNS::DYNDNS: case DNS::DYNDNS:
return {"https://www.dyndns.com/account/services/hosts/add.html"}; return {"https://www.dyndns.com/account/services/hosts/add.html"};
case DNS::NOIP: case DNS::NOIP:

View file

@ -66,10 +66,12 @@ DownloadHandlerImpl::DownloadHandlerImpl(Net::DownloadManager *manager, const Ne
void DownloadHandlerImpl::cancel() void DownloadHandlerImpl::cancel()
{ {
if (m_reply) { if (m_reply)
{
m_reply->abort(); m_reply->abort();
} }
else { else
{
setError(errorCodeToString(QNetworkReply::OperationCanceledError)); setError(errorCodeToString(QNetworkReply::OperationCanceledError));
finish(); finish();
} }
@ -103,7 +105,8 @@ void DownloadHandlerImpl::processFinishedDownload()
qDebug("Download finished: %s", qUtf8Printable(url())); qDebug("Download finished: %s", qUtf8Printable(url()));
// Check if the request was successful // Check if the request was successful
if (m_reply->error() != QNetworkReply::NoError) { if (m_reply->error() != QNetworkReply::NoError)
{
// Failure // Failure
qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(errorCodeToString(m_reply->error()))); qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(errorCodeToString(m_reply->error())));
setError(errorCodeToString(m_reply->error())); setError(errorCodeToString(m_reply->error()));
@ -113,7 +116,8 @@ void DownloadHandlerImpl::processFinishedDownload()
// Check if the server ask us to redirect somewhere else // Check if the server ask us to redirect somewhere else
const QVariant redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); const QVariant redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirection.isValid()) { if (redirection.isValid())
{
handleRedirection(redirection.toUrl()); handleRedirection(redirection.toUrl());
return; return;
} }
@ -123,7 +127,8 @@ void DownloadHandlerImpl::processFinishedDownload()
? Utils::Gzip::decompress(m_reply->readAll()) ? Utils::Gzip::decompress(m_reply->readAll())
: m_reply->readAll(); : m_reply->readAll();
if (m_downloadRequest.saveToFile()) { if (m_downloadRequest.saveToFile())
{
QString filePath; QString filePath;
if (saveToFile(m_result.data, filePath)) if (saveToFile(m_result.data, filePath))
m_result.filePath = filePath; m_result.filePath = filePath;
@ -136,13 +141,15 @@ void DownloadHandlerImpl::processFinishedDownload()
void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal) void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal)
{ {
if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit())) { if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit()))
{
// Total number of bytes is available // Total number of bytes is available
disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize); disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize);
return; return;
} }
if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit())) { if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit()))
{
m_reply->abort(); m_reply->abort();
setError(tr("The file size (%1) exceeds the download limit (%2)") setError(tr("The file size (%1) exceeds the download limit (%2)")
.arg(Utils::Misc::friendlyUnit(bytesTotal) .arg(Utils::Misc::friendlyUnit(bytesTotal)
@ -153,7 +160,8 @@ void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qi
void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl) void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
{ {
if (m_redirectionCount >= MAX_REDIRECTIONS) { if (m_redirectionCount >= MAX_REDIRECTIONS)
{
setError(tr("Exceeded max redirections (%1)").arg(MAX_REDIRECTIONS)); setError(tr("Exceeded max redirections (%1)").arg(MAX_REDIRECTIONS));
finish(); finish();
return; return;
@ -165,7 +173,8 @@ void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString)); qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
// Redirect to magnet workaround // Redirect to magnet workaround
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) { if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive))
{
qDebug("Magnet redirect detected."); qDebug("Magnet redirect detected.");
m_result.status = Net::DownloadStatus::RedirectedToMagnet; m_result.status = Net::DownloadStatus::RedirectedToMagnet;
m_result.magnet = newUrlString; m_result.magnet = newUrlString;
@ -199,7 +208,8 @@ void DownloadHandlerImpl::finish()
QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status) QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status)
{ {
switch (status) { switch (status)
{
case QNetworkReply::HostNotFoundError: case QNetworkReply::HostNotFoundError:
return tr("The remote host name was not found (invalid hostname)"); return tr("The remote host name was not found (invalid hostname)");
case QNetworkReply::OperationCanceledError: case QNetworkReply::OperationCanceledError:

View file

@ -60,7 +60,8 @@ namespace
{ {
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = Preferences::instance()->getNetworkCookies(); QList<QNetworkCookie> cookies = Preferences::instance()->getNetworkCookies();
for (const QNetworkCookie &cookie : asConst(Preferences::instance()->getNetworkCookies())) { for (const QNetworkCookie &cookie : asConst(Preferences::instance()->getNetworkCookies()))
{
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now)) if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
cookies.removeAll(cookie); cookies.removeAll(cookie);
} }
@ -72,7 +73,8 @@ namespace
{ {
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = allCookies(); QList<QNetworkCookie> cookies = allCookies();
for (const QNetworkCookie &cookie : asConst(allCookies())) { for (const QNetworkCookie &cookie : asConst(allCookies()))
{
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now)) if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
cookies.removeAll(cookie); cookies.removeAll(cookie);
} }
@ -87,7 +89,8 @@ namespace
{ {
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = QNetworkCookieJar::cookiesForUrl(url); QList<QNetworkCookie> cookies = QNetworkCookieJar::cookiesForUrl(url);
for (const QNetworkCookie &cookie : asConst(QNetworkCookieJar::cookiesForUrl(url))) { for (const QNetworkCookie &cookie : asConst(QNetworkCookieJar::cookiesForUrl(url)))
{
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now)) if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
cookies.removeAll(cookie); cookies.removeAll(cookie);
} }
@ -99,7 +102,8 @@ namespace
{ {
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = cookieList; QList<QNetworkCookie> cookies = cookieList;
for (const QNetworkCookie &cookie : cookieList) { for (const QNetworkCookie &cookie : cookieList)
{
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now)) if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
cookies.removeAll(cookie); cookies.removeAll(cookie);
} }
@ -172,10 +176,12 @@ Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &down
m_waitingJobs[id].removeOne(downloadHandler); m_waitingJobs[id].removeOne(downloadHandler);
}); });
if (isSequentialService && m_busyServices.contains(id)) { if (isSequentialService && m_busyServices.contains(id))
{
m_waitingJobs[id].enqueue(downloadHandler); m_waitingJobs[id].enqueue(downloadHandler);
} }
else { else
{
qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url())); qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url()));
if (isSequentialService) if (isSequentialService)
m_busyServices.insert(id); m_busyServices.insert(id);
@ -230,27 +236,32 @@ void Net::DownloadManager::applyProxySettings()
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration(); const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
QNetworkProxy proxy; QNetworkProxy proxy;
if (!proxyManager->isProxyOnlyForTorrents() && (proxyConfig.type != ProxyType::None)) { if (!proxyManager->isProxyOnlyForTorrents() && (proxyConfig.type != ProxyType::None))
{
// Proxy enabled // Proxy enabled
proxy.setHostName(proxyConfig.ip); proxy.setHostName(proxyConfig.ip);
proxy.setPort(proxyConfig.port); proxy.setPort(proxyConfig.port);
// Default proxy type is HTTP, we must change if it is SOCKS5 // Default proxy type is HTTP, we must change if it is SOCKS5
if ((proxyConfig.type == ProxyType::SOCKS5) || (proxyConfig.type == ProxyType::SOCKS5_PW)) { if ((proxyConfig.type == ProxyType::SOCKS5) || (proxyConfig.type == ProxyType::SOCKS5_PW))
{
qDebug() << Q_FUNC_INFO << "using SOCKS proxy"; qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setType(QNetworkProxy::Socks5Proxy);
} }
else { else
{
qDebug() << Q_FUNC_INFO << "using HTTP proxy"; qDebug() << Q_FUNC_INFO << "using HTTP proxy";
proxy.setType(QNetworkProxy::HttpProxy); proxy.setType(QNetworkProxy::HttpProxy);
} }
// Authentication? // Authentication?
if (proxyManager->isAuthenticationRequired()) { if (proxyManager->isAuthenticationRequired())
{
qDebug("Proxy requires authentication, authenticating..."); qDebug("Proxy requires authentication, authenticating...");
proxy.setUser(proxyConfig.username); proxy.setUser(proxyConfig.username);
proxy.setPassword(proxyConfig.password); proxy.setPassword(proxyConfig.password);
} }
} }
else { else
{
proxy.setType(QNetworkProxy::NoProxy); proxy.setType(QNetworkProxy::NoProxy);
} }
@ -264,7 +275,8 @@ void Net::DownloadManager::handleReplyFinished(const QNetworkReply *reply)
// in the case when the redirection occurred. // in the case when the redirection occurred.
const ServiceID id = ServiceID::fromURL(reply->request().url()); const ServiceID id = ServiceID::fromURL(reply->request().url());
const auto waitingJobsIter = m_waitingJobs.find(id); const auto waitingJobsIter = m_waitingJobs.find(id);
if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) { if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty())
{
// No more waiting jobs for given ServiceID // No more waiting jobs for given ServiceID
m_busyServices.remove(id); m_busyServices.remove(id);
return; return;

View file

@ -88,26 +88,30 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
{ {
GeoIPDatabase *db = nullptr; GeoIPDatabase *db = nullptr;
QFile file(filename); QFile file(filename);
if (file.size() > MAX_FILE_SIZE) { if (file.size() > MAX_FILE_SIZE)
{
error = tr("Unsupported database file size."); error = tr("Unsupported database file size.");
return nullptr; return nullptr;
} }
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly))
{
error = file.errorString(); error = file.errorString();
return nullptr; return nullptr;
} }
db = new GeoIPDatabase(file.size()); db = new GeoIPDatabase(file.size());
if (file.read(reinterpret_cast<char *>(db->m_data), db->m_size) != db->m_size) { if (file.read(reinterpret_cast<char *>(db->m_data), db->m_size) != db->m_size)
{
error = file.errorString(); error = file.errorString();
delete db; delete db;
return nullptr; return nullptr;
} }
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) { if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error))
{
delete db; delete db;
return nullptr; return nullptr;
} }
@ -118,7 +122,8 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error) GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
{ {
GeoIPDatabase *db = nullptr; GeoIPDatabase *db = nullptr;
if (data.size() > MAX_FILE_SIZE) { if (data.size() > MAX_FILE_SIZE)
{
error = tr("Unsupported database file size."); error = tr("Unsupported database file size.");
return nullptr; return nullptr;
} }
@ -127,7 +132,8 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
memcpy(reinterpret_cast<char *>(db->m_data), data.constData(), db->m_size); memcpy(reinterpret_cast<char *>(db->m_data), data.constData(), db->m_size);
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) { if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error))
{
delete db; delete db;
return nullptr; return nullptr;
} }
@ -161,8 +167,10 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
const uchar *ptr = m_data; const uchar *ptr = m_data;
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i)
for (int j = 0; j < 8; ++j) { {
for (int j = 0; j < 8; ++j)
{
const bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1); const bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1);
// Interpret the left/right record as number // Interpret the left/right record as number
if (right) if (right)
@ -173,16 +181,20 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
memcpy(&idPtr[4 - m_recordBytes], ptr, m_recordBytes); memcpy(&idPtr[4 - m_recordBytes], ptr, m_recordBytes);
fromBigEndian(idPtr, 4); fromBigEndian(idPtr, 4);
if (id == m_nodeCount) { if (id == m_nodeCount)
{
return {}; return {};
} }
if (id > m_nodeCount) { if (id > m_nodeCount)
{
QString country = m_countries.value(id); QString country = m_countries.value(id);
if (country.isEmpty()) { if (country.isEmpty())
{
const quint32 offset = id - m_nodeCount - sizeof(DATA_SECTION_SEPARATOR); const quint32 offset = id - m_nodeCount - sizeof(DATA_SECTION_SEPARATOR);
quint32 tmp = offset + m_indexSize + sizeof(DATA_SECTION_SEPARATOR); quint32 tmp = offset + m_indexSize + sizeof(DATA_SECTION_SEPARATOR);
const QVariant val = readDataField(tmp); const QVariant val = readDataField(tmp);
if (val.userType() == QMetaType::QVariantHash) { if (val.userType() == QMetaType::QVariantHash)
{
country = val.toHash()["country"].toHash()["iso_code"].toString(); country = val.toHash()["country"].toHash()["iso_code"].toString();
m_countries[id] = country; m_countries[id] = country;
} }
@ -198,18 +210,22 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
} }
#define CHECK_METADATA_REQ(key, type) \ #define CHECK_METADATA_REQ(key, type) \
if (!metadata.contains(#key)) { \ if (!metadata.contains(#key)) \
{ \
error = errMsgNotFound.arg(#key); \ error = errMsgNotFound.arg(#key); \
return false; \ return false; \
} \ } \
if (metadata.value(#key).userType() != QMetaType::type) { \ if (metadata.value(#key).userType() != QMetaType::type) \
{ \
error = errMsgInvalid.arg(#key); \ error = errMsgInvalid.arg(#key); \
return false; \ return false; \
} }
#define CHECK_METADATA_OPT(key, type) \ #define CHECK_METADATA_OPT(key, type) \
if (metadata.contains(#key)) { \ if (metadata.contains(#key)) \
if (metadata.value(#key).userType() != QMetaType::type) { \ { \
if (metadata.value(#key).userType() != QMetaType::type) \
{ \
error = errMsgInvalid.arg(#key); \ error = errMsgInvalid.arg(#key); \
return false; \ return false; \
} \ } \
@ -226,21 +242,24 @@ bool GeoIPDatabase::parseMetadata(const QVariantHash &metadata, QString &error)
CHECK_METADATA_REQ(binary_format_minor_version, UShort); CHECK_METADATA_REQ(binary_format_minor_version, UShort);
const uint versionMajor = metadata.value("binary_format_major_version").toUInt(); const uint versionMajor = metadata.value("binary_format_major_version").toUInt();
const uint versionMinor = metadata.value("binary_format_minor_version").toUInt(); const uint versionMinor = metadata.value("binary_format_minor_version").toUInt();
if (versionMajor != 2) { if (versionMajor != 2)
{
error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor); error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor);
return false; return false;
} }
CHECK_METADATA_REQ(ip_version, UShort); CHECK_METADATA_REQ(ip_version, UShort);
m_ipVersion = metadata.value("ip_version").value<quint16>(); m_ipVersion = metadata.value("ip_version").value<quint16>();
if (m_ipVersion != 6) { if (m_ipVersion != 6)
{
error = tr("Unsupported IP version: %1").arg(m_ipVersion); error = tr("Unsupported IP version: %1").arg(m_ipVersion);
return false; return false;
} }
CHECK_METADATA_REQ(record_size, UShort); CHECK_METADATA_REQ(record_size, UShort);
m_recordSize = metadata.value("record_size").value<quint16>(); m_recordSize = metadata.value("record_size").value<quint16>();
if (m_recordSize != 24) { if (m_recordSize != 24)
{
error = tr("Unsupported record size: %1").arg(m_recordSize); error = tr("Unsupported record size: %1").arg(m_recordSize);
return false; return false;
} }
@ -270,7 +289,8 @@ bool GeoIPDatabase::loadDB(QString &error) const
const int nodeSize = m_recordSize / 4; // in bytes const int nodeSize = m_recordSize / 4; // in bytes
const int indexSize = m_nodeCount * nodeSize; const int indexSize = m_nodeCount * nodeSize;
if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR))) if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR)))
|| (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0)) { || (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0))
{
error = tr("Database corrupted: no data section found."); error = tr("Database corrupted: no data section found.");
return false; return false;
} }
@ -282,14 +302,16 @@ QVariantHash GeoIPDatabase::readMetadata() const
{ {
const char *ptr = reinterpret_cast<const char *>(m_data); const char *ptr = reinterpret_cast<const char *>(m_data);
quint32 size = m_size; quint32 size = m_size;
if (m_size > MAX_METADATA_SIZE) { if (m_size > MAX_METADATA_SIZE)
{
ptr += m_size - MAX_METADATA_SIZE; ptr += m_size - MAX_METADATA_SIZE;
size = MAX_METADATA_SIZE; size = MAX_METADATA_SIZE;
} }
const QByteArray data = QByteArray::fromRawData(ptr, size); const QByteArray data = QByteArray::fromRawData(ptr, size);
int index = data.lastIndexOf(METADATA_BEGIN_MARK); int index = data.lastIndexOf(METADATA_BEGIN_MARK);
if (index >= 0) { if (index >= 0)
{
if (m_size > MAX_METADATA_SIZE) if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data index += (m_size - MAX_METADATA_SIZE); // from begin of all data
auto offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK)); auto offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
@ -309,7 +331,8 @@ QVariant GeoIPDatabase::readDataField(quint32 &offset) const
quint32 locOffset = offset; quint32 locOffset = offset;
bool usePointer = false; bool usePointer = false;
if (descr.fieldType == DataType::Pointer) { if (descr.fieldType == DataType::Pointer)
{
usePointer = true; usePointer = true;
// convert offset from data section to global // convert offset from data section to global
locOffset = descr.offset + (m_nodeCount * m_recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR); locOffset = descr.offset + (m_nodeCount * m_recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
@ -318,7 +341,8 @@ QVariant GeoIPDatabase::readDataField(quint32 &offset) const
} }
QVariant fieldValue; QVariant fieldValue;
switch (descr.fieldType) { switch (descr.fieldType)
{
case DataType::Pointer: case DataType::Pointer:
qDebug() << "* Illegal Pointer using"; qDebug() << "* Illegal Pointer using";
break; break;
@ -388,7 +412,8 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
if (availSize < 1) return false; if (availSize < 1) return false;
out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5); out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5);
if (out.fieldType == DataType::Pointer) { if (out.fieldType == DataType::Pointer)
{
const int size = ((dataPtr[0] & 0x18) >> 3); const int size = ((dataPtr[0] & 0x18) >> 3);
if (availSize < (size + 2)) return false; if (availSize < (size + 2)) return false;
@ -406,28 +431,34 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
} }
out.fieldSize = dataPtr[0] & 0x1F; out.fieldSize = dataPtr[0] & 0x1F;
if (out.fieldSize <= 28) { if (out.fieldSize <= 28)
if (out.fieldType == DataType::Unknown) { {
if (out.fieldType == DataType::Unknown)
{
out.fieldType = static_cast<DataType>(dataPtr[1] + 7); out.fieldType = static_cast<DataType>(dataPtr[1] + 7);
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3)) if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3))
return false; return false;
offset += 2; offset += 2;
} }
else { else
{
offset += 1; offset += 1;
} }
} }
else if (out.fieldSize == 29) { else if (out.fieldSize == 29)
{
if (availSize < 2) return false; if (availSize < 2) return false;
out.fieldSize = dataPtr[1] + 29; out.fieldSize = dataPtr[1] + 29;
offset += 2; offset += 2;
} }
else if (out.fieldSize == 30) { else if (out.fieldSize == 30)
{
if (availSize < 3) return false; if (availSize < 3) return false;
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285; out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
offset += 3; offset += 3;
} }
else if (out.fieldSize == 31) { else if (out.fieldSize == 31)
{
if (availSize < 4) return false; if (availSize < 4) return false;
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821; out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821;
offset += 4; offset += 4;
@ -450,7 +481,8 @@ QVariant GeoIPDatabase::readMapValue(quint32 &offset, const quint32 count) const
{ {
QVariantHash map; QVariantHash map;
for (quint32 i = 0; i < count; ++i) { for (quint32 i = 0; i < count; ++i)
{
QVariant field = readDataField(offset); QVariant field = readDataField(offset);
if (field.userType() != QMetaType::QString) if (field.userType() != QMetaType::QString)
return {}; return {};
@ -470,7 +502,8 @@ QVariant GeoIPDatabase::readArrayValue(quint32 &offset, const quint32 count) con
{ {
QVariantList array; QVariantList array;
for (quint32 i = 0; i < count; ++i) { for (quint32 i = 0; i < count; ++i)
{
const QVariant field = readDataField(offset); const QVariant field = readDataField(offset);
if (field.userType() == QVariant::Invalid) if (field.userType() == QVariant::Invalid)
return {}; return {};

View file

@ -74,7 +74,8 @@ private:
const uchar *const data = m_data + offset; const uchar *const data = m_data + offset;
const quint32 availSize = m_size - offset; const quint32 availSize = m_size - offset;
if ((len > 0) && (len <= sizeof(T) && (availSize >= len))) { if ((len > 0) && (len <= sizeof(T) && (availSize >= len)))
{
// copy input data to last 'len' bytes of 'value' // copy input data to last 'len' bytes of 'value'
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len); uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
memcpy(dst, data, len); memcpy(dst, data, len);

View file

@ -140,7 +140,8 @@ QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
QString GeoIPManager::CountryName(const QString &countryISOCode) QString GeoIPManager::CountryName(const QString &countryISOCode)
{ {
static const QHash<QString, QString> countries = { static const QHash<QString, QString> countries =
{
// ISO 3166-1 alpha-2 codes // ISO 3166-1 alpha-2 codes
// http://www.iso.org/iso/home/standards/country_codes/country_names_and_code_elements_txt-temp.htm // http://www.iso.org/iso/home/standards/country_codes/country_names_and_code_elements_txt-temp.htm
@ -404,12 +405,15 @@ QString GeoIPManager::CountryName(const QString &countryISOCode)
void GeoIPManager::configure() void GeoIPManager::configure()
{ {
const bool enabled = Preferences::instance()->resolvePeerCountries(); const bool enabled = Preferences::instance()->resolvePeerCountries();
if (m_enabled != enabled) { if (m_enabled != enabled)
{
m_enabled = enabled; m_enabled = enabled;
if (m_enabled && !m_geoIPDatabase) { if (m_enabled && !m_geoIPDatabase)
{
loadDatabase(); loadDatabase();
} }
else if (!m_enabled) { else if (!m_enabled)
{
delete m_geoIPDatabase; delete m_geoIPDatabase;
m_geoIPDatabase = nullptr; m_geoIPDatabase = nullptr;
} }
@ -418,22 +422,26 @@ void GeoIPManager::configure()
void GeoIPManager::downloadFinished(const DownloadResult &result) void GeoIPManager::downloadFinished(const DownloadResult &result)
{ {
if (result.status != DownloadStatus::Success) { if (result.status != DownloadStatus::Success)
{
LogMsg(tr("Couldn't download IP geolocation database file. Reason: %1").arg(result.errorString), Log::WARNING); LogMsg(tr("Couldn't download IP geolocation database file. Reason: %1").arg(result.errorString), Log::WARNING);
return; return;
} }
bool ok = false; bool ok = false;
const QByteArray data = Utils::Gzip::decompress(result.data, &ok); const QByteArray data = Utils::Gzip::decompress(result.data, &ok);
if (!ok) { if (!ok)
{
LogMsg(tr("Could not decompress IP geolocation database file."), Log::WARNING); LogMsg(tr("Could not decompress IP geolocation database file."), Log::WARNING);
return; return;
} }
QString error; QString error;
GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error); GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error);
if (geoIPDatabase) { if (geoIPDatabase)
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch())) { {
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch()))
{
delete m_geoIPDatabase; delete m_geoIPDatabase;
m_geoIPDatabase = geoIPDatabase; m_geoIPDatabase = geoIPDatabase;
LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.") LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
@ -449,11 +457,13 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
else else
LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO); LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO);
} }
else { else
{
delete geoIPDatabase; delete geoIPDatabase;
} }
} }
else { else
{
LogMsg(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING); LogMsg(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING);
} }
} }

View file

@ -100,7 +100,8 @@ ProxyConfiguration ProxyConfigurationManager::proxyConfiguration() const
void ProxyConfigurationManager::setProxyConfiguration(const ProxyConfiguration &config) void ProxyConfigurationManager::setProxyConfiguration(const ProxyConfiguration &config)
{ {
if (config != m_config) { if (config != m_config)
{
m_config = config; m_config = config;
settings()->storeValue(KEY_TYPE, static_cast<int>(config.type)); settings()->storeValue(KEY_TYPE, static_cast<int>(config.type));
settings()->storeValue(KEY_IP, config.ip); settings()->storeValue(KEY_IP, config.ip);
@ -120,7 +121,8 @@ bool ProxyConfigurationManager::isProxyOnlyForTorrents() const
void ProxyConfigurationManager::setProxyOnlyForTorrents(bool onlyForTorrents) void ProxyConfigurationManager::setProxyOnlyForTorrents(bool onlyForTorrents)
{ {
if (m_isProxyOnlyForTorrents != onlyForTorrents) { if (m_isProxyOnlyForTorrents != onlyForTorrents)
{
settings()->storeValue(KEY_ONLY_FOR_TORRENTS, onlyForTorrents); settings()->storeValue(KEY_ONLY_FOR_TORRENTS, onlyForTorrents);
m_isProxyOnlyForTorrents = onlyForTorrents; m_isProxyOnlyForTorrents = onlyForTorrents;
} }
@ -136,8 +138,10 @@ void ProxyConfigurationManager::configureProxy()
{ {
// Define environment variables for urllib in search engine plugins // Define environment variables for urllib in search engine plugins
QString proxyStrHTTP, proxyStrSOCK; QString proxyStrHTTP, proxyStrSOCK;
if (!m_isProxyOnlyForTorrents) { if (!m_isProxyOnlyForTorrents)
switch (m_config.type) { {
switch (m_config.type)
{
case ProxyType::HTTP_PW: case ProxyType::HTTP_PW:
proxyStrHTTP = QString::fromLatin1("http://%1:%2@%3:%4").arg(m_config.username proxyStrHTTP = QString::fromLatin1("http://%1:%2@%3:%4").arg(m_config.username
, m_config.password, m_config.ip, QString::number(m_config.port)); , m_config.password, m_config.ip, QString::number(m_config.port));

View file

@ -60,7 +60,8 @@ ReverseResolution::~ReverseResolution()
void ReverseResolution::resolve(const QHostAddress &ip) void ReverseResolution::resolve(const QHostAddress &ip)
{ {
const QString *hostname = m_cache.object(ip); const QString *hostname = m_cache.object(ip);
if (hostname) { if (hostname)
{
emit ipResolved(ip, *hostname); emit ipResolved(ip, *hostname);
return; return;
} }
@ -74,7 +75,8 @@ void ReverseResolution::hostResolved(const QHostInfo &host)
{ {
const QHostAddress ip = m_lookups.take(host.lookupId()); const QHostAddress ip = m_lookups.take(host.lookupId());
if (host.error() != QHostInfo::NoError) { if (host.error() != QHostInfo::NoError)
{
emit ipResolved(ip, {}); emit ipResolved(ip, {});
return; return;
} }

View file

@ -67,7 +67,8 @@ namespace
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large // ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance) // Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
for (int i = 0; i < key.length(); ++i) { for (int i = 0; i < key.length(); ++i)
{
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
} }
@ -100,7 +101,8 @@ Smtp::Smtp(QObject *parent)
{ {
static bool needToRegisterMetaType = true; static bool needToRegisterMetaType = true;
if (needToRegisterMetaType) { if (needToRegisterMetaType)
{
qRegisterMetaType<QAbstractSocket::SocketError>(); qRegisterMetaType<QAbstractSocket::SocketError>();
needToRegisterMetaType = false; needToRegisterMetaType = false;
} }
@ -153,18 +155,21 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
m_from = from; m_from = from;
m_rcpt = to; m_rcpt = to;
// Authentication // Authentication
if (pref->getMailNotificationSMTPAuth()) { if (pref->getMailNotificationSMTPAuth())
{
m_username = pref->getMailNotificationSMTPUsername(); m_username = pref->getMailNotificationSMTPUsername();
m_password = pref->getMailNotificationSMTPPassword(); m_password = pref->getMailNotificationSMTPPassword();
} }
// Connect to SMTP server // Connect to SMTP server
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
if (pref->getMailNotificationSMTPSSL()) { if (pref->getMailNotificationSMTPSSL())
{
m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL); m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
m_useSsl = true; m_useSsl = true;
} }
else { else
{
#endif #endif
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT); m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
m_useSsl = false; m_useSsl = false;
@ -178,7 +183,8 @@ void Smtp::readyRead()
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
// SMTP is line-oriented // SMTP is line-oriented
m_buffer += m_socket->readAll(); m_buffer += m_socket->readAll();
while (true) { while (true)
{
const int pos = m_buffer.indexOf("\r\n"); const int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition if (pos < 0) return; // Loop exit condition
const QByteArray line = m_buffer.left(pos); const QByteArray line = m_buffer.left(pos);
@ -187,9 +193,11 @@ void Smtp::readyRead()
// Extract response code // Extract response code
const QByteArray code = line.left(3); const QByteArray code = line.left(3);
switch (m_state) { switch (m_state)
{
case Init: case Init:
if (code[0] == '2') { if (code[0] == '2')
{
// The server may send a multiline greeting/INIT/220 response. // The server may send a multiline greeting/INIT/220 response.
// We wait until it finishes. // We wait until it finishes.
if (line[3] != ' ') if (line[3] != ' ')
@ -197,7 +205,8 @@ void Smtp::readyRead()
// Connection was successful // Connection was successful
ehlo(); ehlo();
} }
else { else
{
logError(QLatin1String("Connection failed, unrecognized reply: ") + line); logError(QLatin1String("Connection failed, unrecognized reply: ") + line);
m_state = Close; m_state = Close;
} }
@ -209,11 +218,13 @@ void Smtp::readyRead()
break; break;
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
case StartTLSSent: case StartTLSSent:
if (code == "220") { if (code == "220")
{
m_socket->startClientEncryption(); m_socket->startClientEncryption();
ehlo(); ehlo();
} }
else { else
{
authenticate(); authenticate();
} }
break; break;
@ -226,59 +237,69 @@ void Smtp::readyRead()
break; break;
case AuthSent: case AuthSent:
case Authenticated: case Authenticated:
if (code[0] == '2') { if (code[0] == '2')
{
qDebug() << "Sending <mail from>..."; qDebug() << "Sending <mail from>...";
m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n"); m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n");
m_socket->flush(); m_socket->flush();
m_state = Rcpt; m_state = Rcpt;
} }
else { else
{
// Authentication failed! // Authentication failed!
logError(QLatin1String("Authentication failed, msg: ") + line); logError(QLatin1String("Authentication failed, msg: ") + line);
m_state = Close; m_state = Close;
} }
break; break;
case Rcpt: case Rcpt:
if (code[0] == '2') { if (code[0] == '2')
{
m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n"); m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n");
m_socket->flush(); m_socket->flush();
m_state = Data; m_state = Data;
} }
else { else
{
logError(QLatin1String("<mail from> was rejected by server, msg: ") + line); logError(QLatin1String("<mail from> was rejected by server, msg: ") + line);
m_state = Close; m_state = Close;
} }
break; break;
case Data: case Data:
if (code[0] == '2') { if (code[0] == '2')
{
m_socket->write("data\r\n"); m_socket->write("data\r\n");
m_socket->flush(); m_socket->flush();
m_state = Body; m_state = Body;
} }
else { else
{
logError(QLatin1String("<Rcpt to> was rejected by server, msg: ") + line); logError(QLatin1String("<Rcpt to> was rejected by server, msg: ") + line);
m_state = Close; m_state = Close;
} }
break; break;
case Body: case Body:
if (code[0] == '3') { if (code[0] == '3')
{
m_socket->write(m_message + "\r\n.\r\n"); m_socket->write(m_message + "\r\n.\r\n");
m_socket->flush(); m_socket->flush();
m_state = Quit; m_state = Quit;
} }
else { else
{
logError(QLatin1String("<data> was rejected by server, msg: ") + line); logError(QLatin1String("<data> was rejected by server, msg: ") + line);
m_state = Close; m_state = Close;
} }
break; break;
case Quit: case Quit:
if (code[0] == '2') { if (code[0] == '2')
{
m_socket->write("QUIT\r\n"); m_socket->write("QUIT\r\n");
m_socket->flush(); m_socket->flush();
// here, we just close. // here, we just close.
m_state = Close; m_state = Close;
} }
else { else
{
logError(QLatin1String("Message was rejected by the server, error: ") + line); logError(QLatin1String("Message was rejected by the server, error: ") + line);
m_state = Close; m_state = Close;
} }
@ -296,10 +317,13 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, cons
QByteArray rv = ""; QByteArray rv = "";
QByteArray line = key.toLatin1() + ": "; QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix; if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value)) { if (!value.contains("=?") && latin1->canEncode(value))
{
bool firstWord = true; bool firstWord = true;
for (const QByteArray &word : asConst(value.toLatin1().split(' '))) { for (const QByteArray &word : asConst(value.toLatin1().split(' ')))
if (line.size() > 78) { {
if (line.size() > 78)
{
rv = rv + line + "\r\n"; rv = rv + line + "\r\n";
line.clear(); line.clear();
} }
@ -310,7 +334,8 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, cons
firstWord = false; firstWord = false;
} }
} }
else { else
{
// The text cannot be losslessly encoded as Latin-1. Therefore, we // The text cannot be losslessly encoded as Latin-1. Therefore, we
// must use base64 encoding. // must use base64 encoding.
const QByteArray utf8 = value.toUtf8(); const QByteArray utf8 = value.toUtf8();
@ -318,8 +343,10 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, cons
const QByteArray base64 = utf8.toBase64(); const QByteArray base64 = utf8.toBase64();
const int ct = base64.length(); const int ct = base64.length();
line += "=?utf-8?b?"; line += "=?utf-8?b?";
for (int i = 0; i < ct; i += 4) { for (int i = 0; i < ct; i += 4)
/*if (line.length() > 72) { {
/*if (line.length() > 72)
{
rv += line + "?\n\r"; rv += line + "?\n\r";
line = " =?utf-8?b?"; line = " =?utf-8?b?";
}*/ }*/
@ -348,14 +375,17 @@ void Smtp::helo()
void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const QString &line) void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const QString &line)
{ {
if (code != "250") { if (code != "250")
{
// Error // Error
if (m_state == EhloSent) { if (m_state == EhloSent)
{
// try to send HELO instead of EHLO // try to send HELO instead of EHLO
qDebug() << "EHLO failed, trying HELO instead..."; qDebug() << "EHLO failed, trying HELO instead...";
helo(); helo();
} }
else { else
{
// Both EHLO and HELO failed, chances are this is NOT // Both EHLO and HELO failed, chances are this is NOT
// a SMTP server // a SMTP server
logError("Both EHLO and HELO failed, msg: " + line); logError("Both EHLO and HELO failed, msg: " + line);
@ -364,20 +394,24 @@ void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const
return; return;
} }
if (m_state != EhloGreetReceived) { if (m_state != EhloGreetReceived)
if (!continued) { {
if (!continued)
{
// greeting only, no extensions // greeting only, no extensions
qDebug() << "No extension"; qDebug() << "No extension";
m_state = EhloDone; m_state = EhloDone;
} }
else { else
{
// greeting followed by extensions // greeting followed by extensions
m_state = EhloGreetReceived; m_state = EhloGreetReceived;
qDebug() << "EHLO greet received"; qDebug() << "EHLO greet received";
return; return;
} }
} }
else { else
{
qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper() qDebug() << Q_FUNC_INFO << "Supported extension: " << line.section(' ', 0, 0).toUpper()
<< line.section(' ', 1); << line.section(' ', 1);
m_extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1); m_extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
@ -387,11 +421,13 @@ void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const
if (m_state != EhloDone) return; if (m_state != EhloDone) return;
if (m_extensions.contains("STARTTLS") && m_useSsl) { if (m_extensions.contains("STARTTLS") && m_useSsl)
{
qDebug() << "STARTTLS"; qDebug() << "STARTTLS";
startTLS(); startTLS();
} }
else { else
{
authenticate(); authenticate();
} }
} }
@ -400,7 +436,8 @@ void Smtp::authenticate()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if (!m_extensions.contains("AUTH") || if (!m_extensions.contains("AUTH") ||
m_username.isEmpty() || m_password.isEmpty()) { m_username.isEmpty() || m_password.isEmpty())
{
// Skip authentication // Skip authentication
qDebug() << "Skipping authentication..."; qDebug() << "Skipping authentication...";
m_state = Authenticated; m_state = Authenticated;
@ -414,19 +451,23 @@ void Smtp::authenticate()
// authentication modes are supported by // authentication modes are supported by
// the server // the server
const QStringList auth = m_extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts); const QStringList auth = m_extensions["AUTH"].toUpper().split(' ', QString::SkipEmptyParts);
if (auth.contains("CRAM-MD5")) { if (auth.contains("CRAM-MD5"))
{
qDebug() << "Using CRAM-MD5 authentication..."; qDebug() << "Using CRAM-MD5 authentication...";
authCramMD5(); authCramMD5();
} }
else if (auth.contains("PLAIN")) { else if (auth.contains("PLAIN"))
{
qDebug() << "Using PLAIN authentication..."; qDebug() << "Using PLAIN authentication...";
authPlain(); authPlain();
} }
else if (auth.contains("LOGIN")) { else if (auth.contains("LOGIN"))
{
qDebug() << "Using LOGIN authentication..."; qDebug() << "Using LOGIN authentication...";
authLogin(); authLogin();
} }
else { else
{
// Skip authentication // Skip authentication
logError("The SMTP server does not seem to support any of the authentications modes " logError("The SMTP server does not seem to support any of the authentications modes "
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, " "we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
@ -453,13 +494,15 @@ void Smtp::startTLS()
void Smtp::authCramMD5(const QByteArray &challenge) void Smtp::authCramMD5(const QByteArray &challenge)
{ {
if (m_state != AuthRequestSent) { if (m_state != AuthRequestSent)
{
m_socket->write("auth cram-md5\r\n"); m_socket->write("auth cram-md5\r\n");
m_socket->flush(); m_socket->flush();
m_authType = AuthCramMD5; m_authType = AuthCramMD5;
m_state = AuthRequestSent; m_state = AuthRequestSent;
} }
else { else
{
const QByteArray response = m_username.toLatin1() + ' ' const QByteArray response = m_username.toLatin1() + ' '
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex(); + hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
m_socket->write(response.toBase64() + "\r\n"); m_socket->write(response.toBase64() + "\r\n");
@ -470,7 +513,8 @@ void Smtp::authCramMD5(const QByteArray &challenge)
void Smtp::authPlain() void Smtp::authPlain()
{ {
if (m_state != AuthRequestSent) { if (m_state != AuthRequestSent)
{
m_authType = AuthPlain; m_authType = AuthPlain;
// Prepare Auth string // Prepare Auth string
QByteArray auth; QByteArray auth;
@ -489,18 +533,21 @@ void Smtp::authPlain()
void Smtp::authLogin() void Smtp::authLogin()
{ {
if ((m_state != AuthRequestSent) && (m_state != AuthUsernameSent)) { if ((m_state != AuthRequestSent) && (m_state != AuthUsernameSent))
{
m_socket->write("auth login\r\n"); m_socket->write("auth login\r\n");
m_socket->flush(); m_socket->flush();
m_authType = AuthLogin; m_authType = AuthLogin;
m_state = AuthRequestSent; m_state = AuthRequestSent;
} }
else if (m_state == AuthRequestSent) { else if (m_state == AuthRequestSent)
{
m_socket->write(m_username.toLatin1().toBase64() + "\r\n"); m_socket->write(m_username.toLatin1().toBase64() + "\r\n");
m_socket->flush(); m_socket->flush();
m_state = AuthUsernameSent; m_state = AuthUsernameSent;
} }
else { else
{
m_socket->write(m_password.toLatin1().toBase64() + "\r\n"); m_socket->write(m_password.toLatin1().toBase64() + "\r\n");
m_socket->flush(); m_socket->flush();
m_state = AuthSent; m_state = AuthSent;

View file

@ -306,11 +306,13 @@ bool Preferences::WinStartup() const
void Preferences::setWinStartup(const bool b) void Preferences::setWinStartup(const bool b)
{ {
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
if (b) { if (b)
{
const QString binPath = '"' + Utils::Fs::toNativePath(qApp->applicationFilePath()) + '"'; const QString binPath = '"' + Utils::Fs::toNativePath(qApp->applicationFilePath()) + '"';
settings.setValue("qBittorrent", binPath); settings.setValue("qBittorrent", binPath);
} }
else { else
{
settings.remove("qBittorrent"); settings.remove("qBittorrent");
} }
} }
@ -530,7 +532,8 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
QVector<Utils::Net::Subnet> ret; QVector<Utils::Net::Subnet> ret;
ret.reserve(subnets.size()); ret.reserve(subnets.size());
for (const QString &rawSubnet : subnets) { for (const QString &rawSubnet : subnets)
{
bool ok = false; bool ok = false;
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok); const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok);
if (ok) if (ok)
@ -977,7 +980,8 @@ void Preferences::setNeverCheckFileAssoc(const bool check)
bool Preferences::isTorrentFileAssocSet() bool Preferences::isTorrentFileAssocSet()
{ {
const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat); const QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
if (settings.value(".torrent/Default").toString() != "qBittorrent") { if (settings.value(".torrent/Default").toString() != "qBittorrent")
{
qDebug(".torrent != qBittorrent"); qDebug(".torrent != qBittorrent");
return false; return false;
} }
@ -1008,13 +1012,15 @@ void Preferences::setTorrentFileAssoc(const bool set)
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat); QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
// .Torrent association // .Torrent association
if (set) { if (set)
{
const QString oldProgId = settings.value(".torrent/Default").toString(); const QString oldProgId = settings.value(".torrent/Default").toString();
if (!oldProgId.isEmpty() && (oldProgId != "qBittorrent")) if (!oldProgId.isEmpty() && (oldProgId != "qBittorrent"))
settings.setValue(".torrent/OpenWithProgids/" + oldProgId, ""); settings.setValue(".torrent/OpenWithProgids/" + oldProgId, "");
settings.setValue(".torrent/Default", "qBittorrent"); settings.setValue(".torrent/Default", "qBittorrent");
} }
else if (isTorrentFileAssocSet()) { else if (isTorrentFileAssocSet())
{
settings.setValue(".torrent/Default", ""); settings.setValue(".torrent/Default", "");
} }
@ -1026,7 +1032,8 @@ void Preferences::setMagnetLinkAssoc(const bool set)
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat); QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
// Magnet association // Magnet association
if (set) { if (set)
{
const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\""; const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\"";
const QString iconStr = '"' + qApp->applicationFilePath() + "\",1"; const QString iconStr = '"' + qApp->applicationFilePath() + "\",1";
@ -1037,7 +1044,8 @@ void Preferences::setMagnetLinkAssoc(const bool set)
settings.setValue("magnet/shell/Default", "open"); settings.setValue("magnet/shell/Default", "open");
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr)); settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr));
} }
else if (isMagnetLinkAssocSet()) { else if (isMagnetLinkAssocSet())
{
settings.remove("magnet"); settings.remove("magnet");
} }
@ -1056,9 +1064,11 @@ bool Preferences::isTorrentFileAssocSet()
{ {
bool isSet = false; bool isSet = false;
const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL); const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
if (torrentId != NULL) { if (torrentId != NULL)
{
const CFStringRef defaultHandlerId = LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer); const CFStringRef defaultHandlerId = LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer);
if (defaultHandlerId != NULL) { if (defaultHandlerId != NULL)
{
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle()); const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo; isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
CFRelease(defaultHandlerId); CFRelease(defaultHandlerId);
@ -1072,7 +1082,8 @@ bool Preferences::isMagnetLinkAssocSet()
{ {
bool isSet = false; bool isSet = false;
const CFStringRef defaultHandlerId = LSCopyDefaultHandlerForURLScheme(magnetUrlScheme); const CFStringRef defaultHandlerId = LSCopyDefaultHandlerForURLScheme(magnetUrlScheme);
if (defaultHandlerId != NULL) { if (defaultHandlerId != NULL)
{
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle()); const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo; isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
CFRelease(defaultHandlerId); CFRelease(defaultHandlerId);
@ -1085,7 +1096,8 @@ void Preferences::setTorrentFileAssoc()
if (isTorrentFileAssocSet()) if (isTorrentFileAssocSet())
return; return;
const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL); const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
if (torrentId != NULL) { if (torrentId != NULL)
{
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle()); const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId); LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId);
CFRelease(torrentId); CFRelease(torrentId);

View file

@ -72,7 +72,8 @@ const Profile *Profile::instance()
QString Profile::location(const SpecialFolder folder) const QString Profile::location(const SpecialFolder folder) const
{ {
QString result; QString result;
switch (folder) { switch (folder)
{
case SpecialFolder::Cache: case SpecialFolder::Cache:
result = m_profileImpl->cacheLocation(); result = m_profileImpl->cacheLocation();
break; break;

View file

@ -83,7 +83,8 @@ QString Private::DefaultProfile::dataLocation() const
const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QLatin1Char('/') + profileName() + QLatin1Char('/'); + QLatin1Char('/') + profileName() + QLatin1Char('/');
if (QDir(legacyDir).exists()) { if (QDir(legacyDir).exists())
{
qWarning("The legacy data directory '%s' is used. It is recommended to move its content to '%s'", qWarning("The legacy data directory '%s' is used. It is recommended to move its content to '%s'",
qUtf8Printable(legacyDir), qUtf8Printable(dataDir)); qUtf8Printable(legacyDir), qUtf8Printable(dataDir));
@ -178,7 +179,8 @@ QString Private::Converter::toPortablePath(const QString &path) const
return path; return path;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (QDir::isAbsolutePath(path)) { if (QDir::isAbsolutePath(path))
{
const QChar driveLeter = path[0].toUpper(); const QChar driveLeter = path[0].toUpper();
const QChar baseDriveLetter = m_baseDir.path()[0].toUpper(); const QChar baseDriveLetter = m_baseDir.path()[0].toUpper();
const bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter); const bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter);

View file

@ -126,7 +126,8 @@ QVariantHash Article::data() const
void Article::markAsRead() void Article::markAsRead()
{ {
if (!m_isRead) { if (!m_isRead)
{
m_isRead = true; m_isRead = true;
m_data[KeyIsRead] = m_isRead; m_data[KeyIsRead] = m_isRead;
emit read(this); emit read(this);

View file

@ -82,7 +82,8 @@ namespace
const QJsonObject jsonObj {jsonDoc.object()}; const QJsonObject jsonObj {jsonDoc.object()};
QVector<RSS::AutoDownloadRule> rules; QVector<RSS::AutoDownloadRule> rules;
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) { for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it)
{
const QJsonValue jsonVal {it.value()}; const QJsonValue jsonVal {it.value()};
if (!jsonVal.isObject()) if (!jsonVal.isObject())
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format.")); throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
@ -177,7 +178,8 @@ QList<AutoDownloadRule> AutoDownloader::rules() const
void AutoDownloader::insertRule(const AutoDownloadRule &rule) void AutoDownloader::insertRule(const AutoDownloadRule &rule)
{ {
if (!hasRule(rule.name())) { if (!hasRule(rule.name()))
{
// Insert new rule // Insert new rule
setRule_impl(rule); setRule_impl(rule);
m_dirty = true; m_dirty = true;
@ -185,7 +187,8 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
emit ruleAdded(rule.name()); emit ruleAdded(rule.name());
resetProcessingQueue(); resetProcessingQueue();
} }
else if (ruleByName(rule.name()) != rule) { else if (ruleByName(rule.name()) != rule)
{
// Update existing rule // Update existing rule
setRule_impl(rule); setRule_impl(rule);
m_dirty = true; m_dirty = true;
@ -211,7 +214,8 @@ bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleN
void AutoDownloader::removeRule(const QString &ruleName) void AutoDownloader::removeRule(const QString &ruleName)
{ {
if (m_rules.contains(ruleName)) { if (m_rules.contains(ruleName))
{
emit ruleAboutToBeRemoved(ruleName); emit ruleAboutToBeRemoved(ruleName);
m_rules.remove(ruleName); m_rules.remove(ruleName);
m_dirty = true; m_dirty = true;
@ -221,7 +225,8 @@ void AutoDownloader::removeRule(const QString &ruleName)
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
{ {
switch (format) { switch (format)
{
case RulesFileFormat::Legacy: case RulesFileFormat::Legacy:
return exportRulesToLegacyFormat(); return exportRulesToLegacyFormat();
default: default:
@ -231,7 +236,8 @@ QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) c
void AutoDownloader::importRules(const QByteArray &data, const AutoDownloader::RulesFileFormat format) void AutoDownloader::importRules(const QByteArray &data, const AutoDownloader::RulesFileFormat format)
{ {
switch (format) { switch (format)
{
case RulesFileFormat::Legacy: case RulesFileFormat::Legacy:
importRulesFromLegacyFormat(data); importRulesFromLegacyFormat(data);
break; break;
@ -286,8 +292,10 @@ QStringList AutoDownloader::smartEpisodeFilters() const
{ {
const QVariant filtersSetting = SettingsStorage::instance()->loadValue(SettingsKey_SmartEpisodeFilter); const QVariant filtersSetting = SettingsStorage::instance()->loadValue(SettingsKey_SmartEpisodeFilter);
if (filtersSetting.isNull()) { if (filtersSetting.isNull())
QStringList filters = { {
QStringList filters =
{
"s(\\d+)e(\\d+)", // Format 1: s01e01 "s(\\d+)e(\\d+)", // Format 1: s01e01
"(\\d+)x(\\d+)", // Format 2: 01x01 "(\\d+)x(\\d+)", // Format 2: 01x01
"(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", // Format 3: 2017.01.01 "(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", // Format 3: 2017.01.01
@ -375,7 +383,8 @@ void AutoDownloader::addJobForArticle(const Article *article)
void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job) void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
{ {
for (AutoDownloadRule &rule : m_rules) { for (AutoDownloadRule &rule : m_rules)
{
if (!rule.isEnabled()) continue; if (!rule.isEnabled()) continue;
if (!rule.feedURLs().contains(job->feedURL)) continue; if (!rule.feedURLs().contains(job->feedURL)) continue;
if (!rule.accepts(job->articleData)) continue; if (!rule.accepts(job->articleData)) continue;
@ -393,13 +402,16 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString(); const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
BitTorrent::Session::instance()->addTorrent(torrentURL, params); BitTorrent::Session::instance()->addTorrent(torrentURL, params);
if (BitTorrent::MagnetUri(torrentURL).isValid()) { if (BitTorrent::MagnetUri(torrentURL).isValid())
if (Feed *feed = Session::instance()->feedByURL(job->feedURL)) { {
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
{
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString())) if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
article->markAsRead(); article->markAsRead();
} }
} }
else { else
{
// waiting for torrent file downloading // waiting for torrent file downloading
m_waitingJobs.insert(torrentURL, job); m_waitingJobs.insert(torrentURL, job);
} }
@ -423,12 +435,14 @@ void AutoDownloader::load()
void AutoDownloader::loadRules(const QByteArray &data) void AutoDownloader::loadRules(const QByteArray &data)
{ {
try { try
{
const auto rules = rulesFromJSON(data); const auto rules = rulesFromJSON(data);
for (const auto &rule : rules) for (const auto &rule : rules)
setRule_impl(rule); setRule_impl(rule);
} }
catch (const ParsingError &error) { catch (const ParsingError &error)
{
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1") LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
.arg(error.message()), Log::CRITICAL); .arg(error.message()), Log::CRITICAL);
} }
@ -438,7 +452,8 @@ void AutoDownloader::loadRulesLegacy()
{ {
const SettingsPtr settings = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss")); const SettingsPtr settings = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss"));
const QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash(); const QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
for (const QVariant &ruleVar : rules) { for (const QVariant &ruleVar : rules)
{
const auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash()); const auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
if (!rule.name().isEmpty()) if (!rule.name().isEmpty())
insertRule(rule); insertRule(rule);
@ -475,7 +490,8 @@ void AutoDownloader::resetProcessingQueue()
m_processingQueue.clear(); m_processingQueue.clear();
if (!m_processingEnabled) return; if (!m_processingEnabled) return;
for (Article *article : asConst(Session::instance()->rootFolder()->articles())) { for (Article *article : asConst(Session::instance()->rootFolder()->articles()))
{
if (!article->isRead() && !article->torrentUrl().isEmpty()) if (!article->isRead() && !article->torrentUrl().isEmpty())
addJobForArticle(article); addJobForArticle(article);
} }
@ -489,13 +505,16 @@ void AutoDownloader::startProcessing()
void AutoDownloader::setProcessingEnabled(const bool enabled) void AutoDownloader::setProcessingEnabled(const bool enabled)
{ {
if (m_processingEnabled != enabled) { if (m_processingEnabled != enabled)
{
m_processingEnabled = enabled; m_processingEnabled = enabled;
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled); SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
if (m_processingEnabled) { if (m_processingEnabled)
{
startProcessing(); startProcessing();
} }
else { else
{
m_processingQueue.clear(); m_processingQueue.clear();
disconnect(Session::instance()->rootFolder(), &Folder::newArticle, this, &AutoDownloader::handleNewArticle); disconnect(Session::instance()->rootFolder(), &Folder::newArticle, this, &AutoDownloader::handleNewArticle);
} }

View file

@ -64,7 +64,8 @@ namespace
QJsonValue triStateBoolToJsonValue(const TriStateBool triStateBool) QJsonValue triStateBoolToJsonValue(const TriStateBool triStateBool)
{ {
switch (static_cast<signed char>(triStateBool)) { switch (static_cast<signed char>(triStateBool))
{
case 0: return false; case 0: return false;
case 1: return true; case 1: return true;
default: return {}; default: return {};
@ -73,7 +74,8 @@ namespace
TriStateBool addPausedLegacyToTriStateBool(const int val) TriStateBool addPausedLegacyToTriStateBool(const int val)
{ {
switch (val) { switch (val)
{
case 1: return TriStateBool::True; // always case 1: return TriStateBool::True; // always
case 2: return TriStateBool::False; // never case 2: return TriStateBool::False; // never
default: return TriStateBool::Undefined; // default default: return TriStateBool::Undefined; // default
@ -82,7 +84,8 @@ namespace
int triStateBoolToAddPausedLegacy(const TriStateBool triStateBool) int triStateBoolToAddPausedLegacy(const TriStateBool triStateBool)
{ {
switch (static_cast<signed char>(triStateBool)) { switch (static_cast<signed char>(triStateBool))
{
case 0: return 2; // never case 0: return 2; // never
case 1: return 1; // always case 1: return 1; // always
default: return 0; // default default: return 0; // default
@ -164,7 +167,8 @@ QString computeEpisodeName(const QString &article)
return {}; return {};
QStringList ret; QStringList ret;
for (int i = 1; i <= match.lastCapturedIndex(); ++i) { for (int i = 1; i <= match.lastCapturedIndex(); ++i)
{
const QString cap = match.captured(i); const QString cap = match.captured(i);
if (cap.isEmpty()) if (cap.isEmpty())
@ -199,8 +203,10 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, cons
Q_ASSERT(!expression.isEmpty()); Q_ASSERT(!expression.isEmpty());
QRegularExpression &regex = m_dataPtr->cachedRegexes[expression]; QRegularExpression &regex = m_dataPtr->cachedRegexes[expression];
if (regex.pattern().isEmpty()) { if (regex.pattern().isEmpty())
regex = QRegularExpression { {
regex = QRegularExpression
{
(isRegex ? expression : Utils::String::wildcardToRegex(expression)) (isRegex ? expression : Utils::String::wildcardToRegex(expression))
, QRegularExpression::CaseInsensitiveOption}; , QRegularExpression::CaseInsensitiveOption};
} }
@ -212,12 +218,14 @@ bool AutoDownloadRule::matchesExpression(const QString &articleTitle, const QStr
{ {
const QRegularExpression whitespace {"\\s+"}; const QRegularExpression whitespace {"\\s+"};
if (expression.isEmpty()) { if (expression.isEmpty())
{
// A regex of the form "expr|" will always match, so do the same for wildcards // A regex of the form "expr|" will always match, so do the same for wildcards
return true; return true;
} }
if (m_dataPtr->useRegex) { if (m_dataPtr->useRegex)
{
const QRegularExpression reg(cachedRegex(expression)); const QRegularExpression reg(cachedRegex(expression));
return reg.match(articleTitle).hasMatch(); return reg.match(articleTitle).hasMatch();
} }
@ -225,7 +233,8 @@ bool AutoDownloadRule::matchesExpression(const QString &articleTitle, const QStr
// Only match if every wildcard token (separated by spaces) is present in the article name. // Only match if every wildcard token (separated by spaces) is present in the article name.
// Order of wildcard tokens is unimportant (if order is important, they should have used *). // Order of wildcard tokens is unimportant (if order is important, they should have used *).
const QStringList wildcards {expression.split(whitespace, QString::SplitBehavior::SkipEmptyParts)}; const QStringList wildcards {expression.split(whitespace, QString::SplitBehavior::SkipEmptyParts)};
for (const QString &wildcard : wildcards) { for (const QString &wildcard : wildcards)
{
const QRegularExpression reg {cachedRegex(wildcard, false)}; const QRegularExpression reg {cachedRegex(wildcard, false)};
if (!reg.match(articleTitle).hasMatch()) if (!reg.match(articleTitle).hasMatch())
return false; return false;
@ -279,7 +288,8 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
const QStringList episodes {matcher.captured(2).split(';')}; const QStringList episodes {matcher.captured(2).split(';')};
const int seasonOurs {season.toInt()}; const int seasonOurs {season.toInt()};
for (QString episode : episodes) { for (QString episode : episodes)
{
if (episode.isEmpty()) if (episode.isEmpty())
continue; continue;
@ -287,7 +297,8 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
while ((episode.size() > 1) && episode.startsWith('0')) while ((episode.size() > 1) && episode.startsWith('0'))
episode = episode.right(episode.size() - 1); episode = episode.right(episode.size() - 1);
if (episode.indexOf('-') != -1) { // Range detected if (episode.indexOf('-') != -1)
{ // Range detected
const QString partialPattern1 {"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"}; const QString partialPattern1 {"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"};
const QString partialPattern2 {"\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)"}; const QString partialPattern2 {"\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)"};
@ -295,21 +306,25 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
QRegularExpressionMatch matcher = cachedRegex(partialPattern1).match(articleTitle); QRegularExpressionMatch matcher = cachedRegex(partialPattern1).match(articleTitle);
bool matched = matcher.hasMatch(); bool matched = matcher.hasMatch();
if (!matched) { if (!matched)
{
matcher = cachedRegex(partialPattern2).match(articleTitle); matcher = cachedRegex(partialPattern2).match(articleTitle);
matched = matcher.hasMatch(); matched = matcher.hasMatch();
} }
if (matched) { if (matched)
{
const int seasonTheirs {matcher.captured(1).toInt()}; const int seasonTheirs {matcher.captured(1).toInt()};
const int episodeTheirs {matcher.captured(2).toInt()}; const int episodeTheirs {matcher.captured(2).toInt()};
if (episode.endsWith('-')) { // Infinite range if (episode.endsWith('-'))
{ // Infinite range
const int episodeOurs {episode.leftRef(episode.size() - 1).toInt()}; const int episodeOurs {episode.leftRef(episode.size() - 1).toInt()};
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs)) if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
return true; return true;
} }
else { // Normal range else
{ // Normal range
const QStringList range {episode.split('-')}; const QStringList range {episode.split('-')};
Q_ASSERT(range.size() == 2); Q_ASSERT(range.size() == 2);
if (range.first().toInt() > range.last().toInt()) if (range.first().toInt() > range.last().toInt())
@ -322,7 +337,8 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
} }
} }
} }
else { // Single number else
{ // Single number
const QString expStr {QString::fromLatin1("\\b(?:s0?%1[ -_\\.]?e0?%2|%1x0?%2)(?:\\D|\\b)").arg(season, episode)}; const QString expStr {QString::fromLatin1("\\b(?:s0?%1[ -_\\.]?e0?%2|%1x0?%2)(?:\\D|\\b)").arg(season, episode)};
if (cachedRegex(expStr).match(articleTitle).hasMatch()) if (cachedRegex(expStr).match(articleTitle).hasMatch())
return true; return true;
@ -343,7 +359,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
// See if this episode has been downloaded before // See if this episode has been downloaded before
const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr); const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
if (previouslyMatched) { if (previouslyMatched)
{
if (!AutoDownloader::instance()->downloadRepacks()) if (!AutoDownloader::instance()->downloadRepacks())
return false; return false;
@ -365,7 +382,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
// If this is a REPACK and PROPER download, add the individual entries to the list // If this is a REPACK and PROPER download, add the individual entries to the list
// so we don't download those // so we don't download those
if (isRepack && isProper) { if (isRepack && isProper)
{
m_dataPtr->lastComputedEpisodes.append(episodeStr + QLatin1String("-REPACK")); m_dataPtr->lastComputedEpisodes.append(episodeStr + QLatin1String("-REPACK"));
m_dataPtr->lastComputedEpisodes.append(episodeStr + QLatin1String("-PROPER")); m_dataPtr->lastComputedEpisodes.append(episodeStr + QLatin1String("-PROPER"));
} }
@ -378,7 +396,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
bool AutoDownloadRule::matches(const QVariantHash &articleData) const bool AutoDownloadRule::matches(const QVariantHash &articleData) const
{ {
const QDateTime articleDate {articleData[Article::KeyDate].toDateTime()}; const QDateTime articleDate {articleData[Article::KeyDate].toDateTime()};
if (ignoreDays() > 0) { if (ignoreDays() > 0)
{
if (lastMatch().isValid() && (articleDate < lastMatch().addDays(ignoreDays()))) if (lastMatch().isValid() && (articleDate < lastMatch().addDays(ignoreDays())))
return false; return false;
} }
@ -404,7 +423,8 @@ bool AutoDownloadRule::accepts(const QVariantHash &articleData)
setLastMatch(articleData[Article::KeyDate].toDateTime()); setLastMatch(articleData[Article::KeyDate].toDateTime());
// If there's a matched episode string, add that to the previously matched list // If there's a matched episode string, add that to the previously matched list
if (!m_dataPtr->lastComputedEpisodes.isEmpty()) { if (!m_dataPtr->lastComputedEpisodes.isEmpty())
{
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisodes); m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisodes);
m_dataPtr->lastComputedEpisodes.clear(); m_dataPtr->lastComputedEpisodes.clear();
} }
@ -474,10 +494,12 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched); const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched);
QStringList previouslyMatched; QStringList previouslyMatched;
if (previouslyMatchedVal.isString()) { if (previouslyMatchedVal.isString())
{
previouslyMatched << previouslyMatchedVal.toString(); previouslyMatched << previouslyMatchedVal.toString();
} }
else { else
{
for (const QJsonValue &val : asConst(previouslyMatchedVal.toArray())) for (const QJsonValue &val : asConst(previouslyMatchedVal.toArray()))
previouslyMatched << val.toString(); previouslyMatched << val.toString();
} }

View file

@ -69,7 +69,8 @@ Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *s
m_dataFileName = QString::fromLatin1(m_uid.toRfc4122().toHex()) + QLatin1String(".json"); m_dataFileName = QString::fromLatin1(m_uid.toRfc4122().toHex()) + QLatin1String(".json");
// Move to new file naming scheme (since v4.1.2) // Move to new file naming scheme (since v4.1.2)
const QString legacyFilename {Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_")) const QString legacyFilename
{Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_"))
+ QLatin1String(".json")}; + QLatin1String(".json")};
const QDir storageDir {m_session->dataFileStorage()->storageDir()}; const QDir storageDir {m_session->dataFileStorage()->storageDir()};
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName))) if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName)))
@ -106,8 +107,10 @@ QList<Article *> Feed::articles() const
void Feed::markAsRead() void Feed::markAsRead()
{ {
const int oldUnreadCount = m_unreadCount; const int oldUnreadCount = m_unreadCount;
for (Article *article : asConst(m_articles)) { for (Article *article : asConst(m_articles))
if (!article->isRead()) { {
if (!article->isRead())
{
article->disconnect(this); article->disconnect(this);
article->markAsRead(); article->markAsRead();
--m_unreadCount; --m_unreadCount;
@ -115,7 +118,8 @@ void Feed::markAsRead()
} }
} }
if (m_unreadCount != oldUnreadCount) { if (m_unreadCount != oldUnreadCount)
{
m_dirty = true; m_dirty = true;
store(); store();
emit unreadCountChanged(this); emit unreadCountChanged(this);
@ -180,7 +184,8 @@ void Feed::handleMaxArticlesPerFeedChanged(const int n)
void Feed::handleIconDownloadFinished(const Net::DownloadResult &result) void Feed::handleIconDownloadFinished(const Net::DownloadResult &result)
{ {
if (result.status == Net::DownloadStatus::Success) { if (result.status == Net::DownloadStatus::Success)
{
m_iconPath = Utils::Fs::toUniformPath(result.filePath); m_iconPath = Utils::Fs::toUniformPath(result.filePath);
emit iconLoaded(this); emit iconLoaded(this);
} }
@ -195,13 +200,15 @@ void Feed::handleDownloadFinished(const Net::DownloadResult &result)
{ {
m_downloadHandler = nullptr; // will be deleted by DownloadManager later m_downloadHandler = nullptr; // will be deleted by DownloadManager later
if (result.status == Net::DownloadStatus::Success) { if (result.status == Net::DownloadStatus::Success)
{
LogMsg(tr("RSS feed at '%1' is successfully downloaded. Starting to parse it.") LogMsg(tr("RSS feed at '%1' is successfully downloaded. Starting to parse it.")
.arg(result.url)); .arg(result.url));
// Parse the download RSS // Parse the download RSS
m_parser->parse(result.data); m_parser->parse(result.data);
} }
else { else
{
m_isLoading = false; m_isLoading = false;
m_hasError = true; m_hasError = true;
@ -216,13 +223,15 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
{ {
m_hasError = !result.error.isEmpty(); m_hasError = !result.error.isEmpty();
if (!result.title.isEmpty() && (title() != result.title)) { if (!result.title.isEmpty() && (title() != result.title))
{
m_title = result.title; m_title = result.title;
m_dirty = true; m_dirty = true;
emit titleChanged(this); emit titleChanged(this);
} }
if (!result.lastBuildDate.isEmpty()) { if (!result.lastBuildDate.isEmpty())
{
m_lastBuildDate = result.lastBuildDate; m_lastBuildDate = result.lastBuildDate;
m_dirty = true; m_dirty = true;
} }
@ -234,7 +243,8 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
const int newArticlesCount = updateArticles(result.articles); const int newArticlesCount = updateArticles(result.articles);
store(); store();
if (m_hasError) { if (m_hasError)
{
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error) LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
, Log::WARNING); , Log::WARNING);
} }
@ -249,16 +259,19 @@ void Feed::load()
{ {
QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName)); QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName));
if (!file.exists()) { if (!file.exists())
{
loadArticlesLegacy(); loadArticlesLegacy();
m_dirty = true; m_dirty = true;
store(); // convert to new format store(); // convert to new format
} }
else if (file.open(QFile::ReadOnly)) { else if (file.open(QFile::ReadOnly))
{
loadArticles(file.readAll()); loadArticles(file.readAll());
file.close(); file.close();
} }
else { else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2") LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(m_dataFileName, file.errorString()) .arg(m_dataFileName, file.errorString())
, Log::WARNING); , Log::WARNING);
@ -269,28 +282,33 @@ void Feed::loadArticles(const QByteArray &data)
{ {
QJsonParseError jsonError; QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError) { if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse RSS Session data. Error: %1").arg(jsonError.errorString()) LogMsg(tr("Couldn't parse RSS Session data. Error: %1").arg(jsonError.errorString())
, Log::WARNING); , Log::WARNING);
return; return;
} }
if (!jsonDoc.isArray()) { if (!jsonDoc.isArray())
{
LogMsg(tr("Couldn't load RSS Session data. Invalid data format."), Log::WARNING); LogMsg(tr("Couldn't load RSS Session data. Invalid data format."), Log::WARNING);
return; return;
} }
const QJsonArray jsonArr = jsonDoc.array(); const QJsonArray jsonArr = jsonDoc.array();
int i = -1; int i = -1;
for (const QJsonValue &jsonVal : jsonArr) { for (const QJsonValue &jsonVal : jsonArr)
{
++i; ++i;
if (!jsonVal.isObject()) { if (!jsonVal.isObject())
{
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i) LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i)
, Log::WARNING); , Log::WARNING);
continue; continue;
} }
try { try
{
auto article = new Article(this, jsonVal.toObject()); auto article = new Article(this, jsonVal.toObject());
if (!addArticle(article)) if (!addArticle(article))
delete article; delete article;
@ -304,13 +322,15 @@ void Feed::loadArticlesLegacy()
const SettingsPtr qBTRSSFeeds = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss-feeds")); const SettingsPtr qBTRSSFeeds = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss-feeds"));
const QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash(); const QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash();
for (const QVariant &var : asConst(allOldItems.value(m_url).toList())) { for (const QVariant &var : asConst(allOldItems.value(m_url).toList()))
{
auto hash = var.toHash(); auto hash = var.toHash();
// update legacy keys // update legacy keys
hash[Article::KeyLink] = hash.take(QLatin1String("news_link")); hash[Article::KeyLink] = hash.take(QLatin1String("news_link"));
hash[Article::KeyTorrentURL] = hash.take(QLatin1String("torrent_url")); hash[Article::KeyTorrentURL] = hash.take(QLatin1String("torrent_url"));
hash[Article::KeyIsRead] = hash.take(QLatin1String("read")); hash[Article::KeyIsRead] = hash.take(QLatin1String("read"));
try { try
{
auto article = new Article(this, hash); auto article = new Article(this, hash);
if (!addArticle(article)) if (!addArticle(article))
delete article; delete article;
@ -353,7 +373,8 @@ bool Feed::addArticle(Article *article)
m_articles[article->guid()] = article; m_articles[article->guid()] = article;
m_articlesByDate.insert(lowerBound, article); m_articlesByDate.insert(lowerBound, article);
if (!article->isRead()) { if (!article->isRead())
{
increaseUnreadCount(); increaseUnreadCount();
connect(article, &Article::read, this, &Feed::handleArticleRead); connect(article, &Article::read, this, &Feed::handleArticleRead);
} }
@ -414,12 +435,14 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
QDateTime dummyPubDate {QDateTime::currentDateTime()}; QDateTime dummyPubDate {QDateTime::currentDateTime()};
QVector<QVariantHash> newArticles; QVector<QVariantHash> newArticles;
newArticles.reserve(loadedArticles.size()); newArticles.reserve(loadedArticles.size());
for (QVariantHash article : loadedArticles) { for (QVariantHash article : loadedArticles)
{
// If article has no publication date we use feed update time as a fallback. // If article has no publication date we use feed update time as a fallback.
// To prevent processing of "out-of-limit" articles we must not assign dates // To prevent processing of "out-of-limit" articles we must not assign dates
// that are earlier than the dates of existing articles. // that are earlier than the dates of existing articles.
const Article *existingArticle = articleByGUID(article[Article::KeyId].toString()); const Article *existingArticle = articleByGUID(article[Article::KeyId].toString());
if (existingArticle) { if (existingArticle)
{
dummyPubDate = existingArticle->date().addMSecs(-1); dummyPubDate = existingArticle->date().addMSecs(-1);
continue; continue;
} }
@ -462,7 +485,8 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
int newArticlesCount = 0; int newArticlesCount = 0;
std::for_each(sortData.crbegin(), sortData.crend(), [this, &newArticlesCount](const ArticleSortAdaptor &a) std::for_each(sortData.crbegin(), sortData.crend(), [this, &newArticlesCount](const ArticleSortAdaptor &a)
{ {
if (a.second) { if (a.second)
{
addArticle(new Article {this, *a.second}); addArticle(new Article {this, *a.second});
++newArticlesCount; ++newArticlesCount;
} }
@ -482,7 +506,8 @@ QJsonValue Feed::toJsonValue(const bool withData) const
jsonObj.insert(KEY_UID, uid().toString()); jsonObj.insert(KEY_UID, uid().toString());
jsonObj.insert(KEY_URL, url()); jsonObj.insert(KEY_URL, url());
if (withData) { if (withData)
{
jsonObj.insert(KEY_TITLE, title()); jsonObj.insert(KEY_TITLE, title());
jsonObj.insert(KEY_LASTBUILDDATE, lastBuildDate()); jsonObj.insert(KEY_LASTBUILDDATE, lastBuildDate());
jsonObj.insert(KEY_ISLOADING, isLoading()); jsonObj.insert(KEY_ISLOADING, isLoading());
@ -499,7 +524,8 @@ QJsonValue Feed::toJsonValue(const bool withData) const
void Feed::handleSessionProcessingEnabledChanged(const bool enabled) void Feed::handleSessionProcessingEnabledChanged(const bool enabled)
{ {
if (enabled) { if (enabled)
{
downloadIcon(); downloadIcon();
disconnect(m_session, &Session::processingStateChanged disconnect(m_session, &Session::processingStateChanged
, this, &Feed::handleSessionProcessingEnabledChanged); , this, &Feed::handleSessionProcessingEnabledChanged);

View file

@ -57,7 +57,8 @@ QList<Article *> Folder::articles() const
{ {
QList<Article *> news; QList<Article *> news;
for (Item *item : asConst(items())) { for (Item *item : asConst(items()))
{
int n = news.size(); int n = news.size();
news << item->articles(); news << item->articles();
std::inplace_merge(news.begin(), news.begin() + n, news.end() std::inplace_merge(news.begin(), news.begin() + n, news.end()

View file

@ -47,7 +47,8 @@ Item::~Item() {}
void Item::setPath(const QString &path) void Item::setPath(const QString &path)
{ {
if (path != m_path) { if (path != m_path)
{
m_path = path; m_path = path;
emit pathChanged(this); emit pathChanged(this);
} }
@ -69,7 +70,8 @@ bool Item::isValidPath(const QString &path)
QString(R"(\A[^\%1]+(\%1[^\%1]+)*\z)").arg(Item::PathSeparator) QString(R"(\A[^\%1]+(\%1[^\%1]+)*\z)").arg(Item::PathSeparator)
, QRegularExpression::DontCaptureOption); , QRegularExpression::DontCaptureOption);
if (path.isEmpty() || !re.match(path).hasMatch()) { if (path.isEmpty() || !re.match(path).hasMatch())
{
qDebug() << "Incorrect RSS Item path:" << path; qDebug() << "Incorrect RSS Item path:" << path;
return false; return false;
} }
@ -93,7 +95,8 @@ QStringList Item::expandPath(const QString &path)
// return result; // return result;
int index = 0; int index = 0;
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0) { while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
{
result << path.left(index); result << path.left(index);
++index; ++index;
} }

View file

@ -53,7 +53,8 @@ namespace
// http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent // http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
// http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent // http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
// http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent // http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
static const QHash<QString, QString> HTMLEntities { static const QHash<QString, QString> HTMLEntities
{
{"nbsp", "&#160;"}, // no-break space = non-breaking space, U+00A0 ISOnum {"nbsp", "&#160;"}, // no-break space = non-breaking space, U+00A0 ISOnum
{"iexcl", "&#161;"}, // inverted exclamation mark, U+00A1 ISOnum {"iexcl", "&#161;"}, // inverted exclamation mark, U+00A1 ISOnum
{"cent", "&#162;"}, // cent sign, U+00A2 ISOnum {"cent", "&#162;"}, // cent sign, U+00A2 ISOnum
@ -359,17 +360,20 @@ namespace
// Ported to Qt from KDElibs4 // Ported to Qt from KDElibs4
QDateTime parseDate(const QString &string) QDateTime parseDate(const QString &string)
{ {
const char shortDay[][4] = { const char shortDay[][4] =
{
"Mon", "Tue", "Wed", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat", "Thu", "Fri", "Sat",
"Sun" "Sun"
}; };
const char longDay[][10] = { const char longDay[][10] =
{
"Monday", "Tuesday", "Wednesday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Thursday", "Friday", "Saturday",
"Sunday" "Sunday"
}; };
const char shortMonth[][4] = { const char shortMonth[][4] =
{
"Jan", "Feb", "Mar", "Apr", "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec" "Sep", "Oct", "Nov", "Dec"
@ -389,7 +393,8 @@ namespace
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"); QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
QStringList parts; QStringList parts;
if (!str.indexOf(rx)) { if (!str.indexOf(rx))
{
// Check that if date has '-' separators, both separators are '-'. // Check that if date has '-' separators, both separators are '-'.
parts = rx.capturedTexts(); parts = rx.capturedTexts();
const bool h1 = (parts[3] == QLatin1String("-")); const bool h1 = (parts[3] == QLatin1String("-"));
@ -397,7 +402,8 @@ namespace
if (h1 != h2) if (h1 != h2)
return QDateTime::currentDateTime(); return QDateTime::currentDateTime();
} }
else { else
{
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY" // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"); rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
if (str.indexOf(rx)) if (str.indexOf(rx))
@ -421,7 +427,8 @@ namespace
return QDateTime::currentDateTime(); return QDateTime::currentDateTime();
int second = 0; int second = 0;
if (!parts[nsec].isEmpty()) { if (!parts[nsec].isEmpty())
{
second = parts[nsec].toInt(&ok[0]); second = parts[nsec].toInt(&ok[0]);
if (!ok[0]) if (!ok[0])
return QDateTime::currentDateTime(); return QDateTime::currentDateTime();
@ -433,7 +440,8 @@ namespace
int month = 0; int month = 0;
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month); for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
int dayOfWeek = -1; int dayOfWeek = -1;
if (!parts[nwday].isEmpty()) { if (!parts[nwday].isEmpty())
{
// Look up the weekday name // Look up the weekday name
while ((++dayOfWeek < 7) && (shortDay[dayOfWeek] != parts[nwday])); while ((++dayOfWeek < 7) && (shortDay[dayOfWeek] != parts[nwday]));
if (dayOfWeek >= 7) if (dayOfWeek >= 7)
@ -444,7 +452,8 @@ namespace
// || (dayOfWeek < 0 && format == RFCDateDay)) // || (dayOfWeek < 0 && format == RFCDateDay))
// return QDateTime; // return QDateTime;
const int i = parts[nyear].size(); const int i = parts[nyear].size();
if (i < 4) { if (i < 4)
{
// It's an obsolete year specification with less than 4 digits // It's an obsolete year specification with less than 4 digits
year += ((i == 2) && (year < 50)) ? 2000 : 1900; year += ((i == 2) && (year < 50)) ? 2000 : 1900;
} }
@ -452,9 +461,11 @@ namespace
// Parse the UTC offset part // Parse the UTC offset part
int offset = 0; // set default to '-0000' int offset = 0; // set default to '-0000'
bool negOffset = false; bool negOffset = false;
if (parts.count() > 10) { if (parts.count() > 10)
{
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$"); rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
if (!parts[10].indexOf(rx)) { if (!parts[10].indexOf(rx))
{
// It's a UTC offset ±hhmm // It's a UTC offset ±hhmm
parts = rx.capturedTexts(); parts = rx.capturedTexts();
offset = parts[2].toInt(&ok[0]) * 3600; offset = parts[2].toInt(&ok[0]) * 3600;
@ -466,13 +477,16 @@ namespace
if (negOffset) if (negOffset)
offset = -offset; offset = -offset;
} }
else { else
{
// Check for an obsolete time zone name // Check for an obsolete time zone name
const QByteArray zone = parts[10].toLatin1(); const QByteArray zone = parts[10].toLatin1();
if ((zone.length() == 1) && (isalpha(zone[0])) && (toupper(zone[0]) != 'J')) { if ((zone.length() == 1) && (isalpha(zone[0])) && (toupper(zone[0]) != 'J'))
{
negOffset = true; // military zone: RFC 2822 treats as '-0000' negOffset = true; // military zone: RFC 2822 treats as '-0000'
} }
else if ((zone != "UT") && (zone != "GMT")) { // treated as '+0000' else if ((zone != "UT") && (zone != "GMT"))
{ // treated as '+0000'
offset = (zone == "EDT") offset = (zone == "EDT")
? -4 * 3600 ? -4 * 3600
: ((zone == "EST") || (zone == "CDT")) : ((zone == "EST") || (zone == "CDT"))
@ -484,7 +498,8 @@ namespace
: (zone == "PST") : (zone == "PST")
? -8 * 3600 ? -8 * 3600
: 0; : 0;
if (!offset) { if (!offset)
{
// Check for any other alphabetic time zone // Check for any other alphabetic time zone
bool nonalpha = false; bool nonalpha = false;
for (int i = 0, end = zone.size(); (i < end) && !nonalpha; ++i) for (int i = 0, end = zone.size(); (i < end) && !nonalpha; ++i)
@ -509,7 +524,8 @@ namespace
if (!result.isValid()) if (!result.isValid())
return QDateTime::currentDateTime(); // invalid date/time return QDateTime::currentDateTime(); // invalid date/time
if (leapSecond) { if (leapSecond)
{
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
// Convert the time to UTC and check that it is 00:00:00. // Convert the time to UTC and check that it is 00:00:00.
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
@ -548,11 +564,15 @@ void Parser::parse_impl(const QByteArray &feedData)
xml.setEntityResolver(&resolver); xml.setEntityResolver(&resolver);
bool foundChannel = false; bool foundChannel = false;
while (xml.readNextStartElement()) { while (xml.readNextStartElement())
if (xml.name() == "rss") { {
if (xml.name() == "rss")
{
// Find channels // Find channels
while (xml.readNextStartElement()) { while (xml.readNextStartElement())
if (xml.name() == "channel") { {
if (xml.name() == "channel")
{
parseRSSChannel(xml); parseRSSChannel(xml);
foundChannel = true; foundChannel = true;
break; break;
@ -563,7 +583,8 @@ void Parser::parse_impl(const QByteArray &feedData)
} }
break; break;
} }
if (xml.name() == "feed") { // Atom feed if (xml.name() == "feed")
{ // Atom feed
parseAtomChannel(xml); parseAtomChannel(xml);
foundChannel = true; foundChannel = true;
break; break;
@ -573,10 +594,12 @@ void Parser::parse_impl(const QByteArray &feedData)
xml.skipCurrentElement(); xml.skipCurrentElement();
} }
if (!foundChannel) { if (!foundChannel)
{
m_result.error = tr("Invalid RSS feed."); m_result.error = tr("Invalid RSS feed.");
} }
else if (xml.hasError()) { else if (xml.hasError())
{
m_result.error = tr("%1 (line: %2, column: %3, offset: %4).") m_result.error = tr("%1 (line: %2, column: %3, offset: %4).")
.arg(xml.errorString()).arg(xml.lineNumber()) .arg(xml.errorString()).arg(xml.lineNumber())
.arg(xml.columnNumber()).arg(xml.characterOffset()); .arg(xml.columnNumber()).arg(xml.characterOffset());
@ -592,43 +615,53 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
QVariantHash article; QVariantHash article;
QString altTorrentUrl; QString altTorrentUrl;
while (!xml.atEnd()) { while (!xml.atEnd())
{
xml.readNext(); xml.readNext();
const QString name(xml.name().toString()); const QString name(xml.name().toString());
if (xml.isEndElement() && (name == QLatin1String("item"))) if (xml.isEndElement() && (name == QLatin1String("item")))
break; break;
if (xml.isStartElement()) { if (xml.isStartElement())
if (name == QLatin1String("title")) { {
if (name == QLatin1String("title"))
{
article[Article::KeyTitle] = xml.readElementText().trimmed(); article[Article::KeyTitle] = xml.readElementText().trimmed();
} }
else if (name == QLatin1String("enclosure")) { else if (name == QLatin1String("enclosure"))
{
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent")) if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString(); article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
else if (xml.attributes().value("type").isEmpty()) else if (xml.attributes().value("type").isEmpty())
altTorrentUrl = xml.attributes().value(QLatin1String("url")).toString(); altTorrentUrl = xml.attributes().value(QLatin1String("url")).toString();
} }
else if (name == QLatin1String("link")) { else if (name == QLatin1String("link"))
{
const QString text {xml.readElementText().trimmed()}; const QString text {xml.readElementText().trimmed()};
if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive)) if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL
else else
article[Article::KeyLink] = text; article[Article::KeyLink] = text;
} }
else if (name == QLatin1String("description")) { else if (name == QLatin1String("description"))
{
article[Article::KeyDescription] = xml.readElementText(QXmlStreamReader::IncludeChildElements); article[Article::KeyDescription] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} }
else if (name == QLatin1String("pubDate")) { else if (name == QLatin1String("pubDate"))
{
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed()); article[Article::KeyDate] = parseDate(xml.readElementText().trimmed());
} }
else if (name == QLatin1String("author")) { else if (name == QLatin1String("author"))
{
article[Article::KeyAuthor] = xml.readElementText().trimmed(); article[Article::KeyAuthor] = xml.readElementText().trimmed();
} }
else if (name == QLatin1String("guid")) { else if (name == QLatin1String("guid"))
{
article[Article::KeyId] = xml.readElementText().trimmed(); article[Article::KeyId] = xml.readElementText().trimmed();
} }
else { else
{
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements); article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} }
} }
@ -642,24 +675,31 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
void Parser::parseRSSChannel(QXmlStreamReader &xml) void Parser::parseRSSChannel(QXmlStreamReader &xml)
{ {
while (!xml.atEnd()) { while (!xml.atEnd())
{
xml.readNext(); xml.readNext();
if (xml.isStartElement()) { if (xml.isStartElement())
if (xml.name() == QLatin1String("title")) { {
if (xml.name() == QLatin1String("title"))
{
m_result.title = xml.readElementText(); m_result.title = xml.readElementText();
} }
else if (xml.name() == QLatin1String("lastBuildDate")) { else if (xml.name() == QLatin1String("lastBuildDate"))
{
const QString lastBuildDate = xml.readElementText(); const QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) { if (!lastBuildDate.isEmpty())
if (m_result.lastBuildDate == lastBuildDate) { {
if (m_result.lastBuildDate == lastBuildDate)
{
qDebug() << "The RSS feed has not changed since last time, aborting parsing."; qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return; return;
} }
m_result.lastBuildDate = lastBuildDate; m_result.lastBuildDate = lastBuildDate;
} }
} }
else if (xml.name() == QLatin1String("item")) { else if (xml.name() == QLatin1String("item"))
{
parseRssArticle(xml); parseRssArticle(xml);
} }
} }
@ -671,18 +711,22 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
QVariantHash article; QVariantHash article;
bool doubleContent = false; bool doubleContent = false;
while (!xml.atEnd()) { while (!xml.atEnd())
{
xml.readNext(); xml.readNext();
const QString name(xml.name().toString()); const QString name(xml.name().toString());
if (xml.isEndElement() && (name == QLatin1String("entry"))) if (xml.isEndElement() && (name == QLatin1String("entry")))
break; break;
if (xml.isStartElement()) { if (xml.isStartElement())
if (name == QLatin1String("title")) { {
if (name == QLatin1String("title"))
{
article[Article::KeyTitle] = xml.readElementText().trimmed(); article[Article::KeyTitle] = xml.readElementText().trimmed();
} }
else if (name == QLatin1String("link")) { else if (name == QLatin1String("link"))
{
const QString link = (xml.attributes().isEmpty() const QString link = (xml.attributes().isEmpty()
? xml.readElementText().trimmed() ? xml.readElementText().trimmed()
: xml.attributes().value(QLatin1String("href")).toString()); : xml.attributes().value(QLatin1String("href")).toString());
@ -696,8 +740,10 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
article[Article::KeyLink] = (m_baseUrl.isEmpty() ? link : m_baseUrl + link); article[Article::KeyLink] = (m_baseUrl.isEmpty() ? link : m_baseUrl + link);
} }
else if ((name == QLatin1String("summary")) || (name == QLatin1String("content"))) { else if ((name == QLatin1String("summary")) || (name == QLatin1String("content")))
if (doubleContent) { // Duplicate content -> ignore {
if (doubleContent)
{ // Duplicate content -> ignore
xml.skipCurrentElement(); xml.skipCurrentElement();
continue; continue;
} }
@ -705,28 +751,34 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
// Try to also parse broken articles, which don't use html '&' escapes // Try to also parse broken articles, which don't use html '&' escapes
// Actually works great for non-broken content too // Actually works great for non-broken content too
const QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements).trimmed(); const QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
if (!feedText.isEmpty()) { if (!feedText.isEmpty())
{
article[Article::KeyDescription] = feedText; article[Article::KeyDescription] = feedText;
doubleContent = true; doubleContent = true;
} }
} }
else if (name == QLatin1String("updated")) { else if (name == QLatin1String("updated"))
{
// ATOM uses standard compliant date, don't do fancy stuff // ATOM uses standard compliant date, don't do fancy stuff
const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate); const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime()); article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
} }
else if (name == QLatin1String("author")) { else if (name == QLatin1String("author"))
while (xml.readNextStartElement()) { {
while (xml.readNextStartElement())
{
if (xml.name() == QLatin1String("name")) if (xml.name() == QLatin1String("name"))
article[Article::KeyAuthor] = xml.readElementText().trimmed(); article[Article::KeyAuthor] = xml.readElementText().trimmed();
else else
xml.skipCurrentElement(); xml.skipCurrentElement();
} }
} }
else if (name == QLatin1String("id")) { else if (name == QLatin1String("id"))
{
article[Article::KeyId] = xml.readElementText().trimmed(); article[Article::KeyId] = xml.readElementText().trimmed();
} }
else { else
{
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements); article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
} }
} }
@ -739,24 +791,31 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
{ {
m_baseUrl = xml.attributes().value("xml:base").toString(); m_baseUrl = xml.attributes().value("xml:base").toString();
while (!xml.atEnd()) { while (!xml.atEnd())
{
xml.readNext(); xml.readNext();
if (xml.isStartElement()) { if (xml.isStartElement())
if (xml.name() == QLatin1String("title")) { {
if (xml.name() == QLatin1String("title"))
{
m_result.title = xml.readElementText(); m_result.title = xml.readElementText();
} }
else if (xml.name() == QLatin1String("updated")) { else if (xml.name() == QLatin1String("updated"))
{
const QString lastBuildDate = xml.readElementText(); const QString lastBuildDate = xml.readElementText();
if (!lastBuildDate.isEmpty()) { if (!lastBuildDate.isEmpty())
if (m_result.lastBuildDate == lastBuildDate) { {
if (m_result.lastBuildDate == lastBuildDate)
{
qDebug() << "The RSS feed has not changed since last time, aborting parsing."; qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
return; return;
} }
m_result.lastBuildDate = lastBuildDate; m_result.lastBuildDate = lastBuildDate;
} }
} }
else if (xml.name() == QLatin1String("entry")) { else if (xml.name() == QLatin1String("entry"))
{
parseAtomArticle(xml); parseAtomArticle(xml);
} }
} }
@ -776,14 +835,16 @@ void Parser::addArticle(QVariantHash article)
if (localId.toString().isEmpty()) if (localId.toString().isEmpty())
localId = article.value(Article::KeyTitle); localId = article.value(Article::KeyTitle);
if (localId.toString().isEmpty()) { if (localId.toString().isEmpty())
{
// The article could not be uniquely identified // The article could not be uniquely identified
// since it has no appropriate data. // since it has no appropriate data.
// Just ignore it. // Just ignore it.
return; return;
} }
if (m_articleIDs.contains(localId.toString())) { if (m_articleIDs.contains(localId.toString()))
{
// The article could not be uniquely identified // The article could not be uniquely identified
// since the Feed has duplicate identifiers. // since the Feed has duplicate identifiers.
// Just ignore it. // Just ignore it.

View file

@ -97,7 +97,8 @@ Session::Session()
load(); load();
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh); connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
if (m_processingEnabled) { if (m_processingEnabled)
{
m_refreshTimer.start(m_refreshInterval * MsecsPerMin); m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
refresh(); refresh();
} }
@ -155,7 +156,8 @@ bool Session::addFolder(const QString &path, QString *error)
bool Session::addFeed(const QString &url, const QString &path, QString *error) bool Session::addFeed(const QString &url, const QString &path, QString *error)
{ {
if (m_feedsByURL.contains(url)) { if (m_feedsByURL.contains(url))
{
if (error) if (error)
*error = tr("RSS feed with given URL already exists: %1.").arg(url); *error = tr("RSS feed with given URL already exists: %1.").arg(url);
return false; return false;
@ -174,14 +176,16 @@ bool Session::addFeed(const QString &url, const QString &path, QString *error)
bool Session::moveItem(const QString &itemPath, const QString &destPath, QString *error) bool Session::moveItem(const QString &itemPath, const QString &destPath, QString *error)
{ {
if (itemPath.isEmpty()) { if (itemPath.isEmpty())
{
if (error) if (error)
*error = tr("Cannot move root folder."); *error = tr("Cannot move root folder.");
return false; return false;
} }
auto item = m_itemsByPath.value(itemPath); auto item = m_itemsByPath.value(itemPath);
if (!item) { if (!item)
{
if (error) if (error)
*error = tr("Item doesn't exist: %1.").arg(itemPath); *error = tr("Item doesn't exist: %1.").arg(itemPath);
return false; return false;
@ -200,7 +204,8 @@ bool Session::moveItem(Item *item, const QString &destPath, QString *error)
return false; return false;
auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path()))); auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
if (srcFolder != destFolder) { if (srcFolder != destFolder)
{
srcFolder->removeItem(item); srcFolder->removeItem(item);
destFolder->addItem(item); destFolder->addItem(item);
} }
@ -212,14 +217,16 @@ bool Session::moveItem(Item *item, const QString &destPath, QString *error)
bool Session::removeItem(const QString &itemPath, QString *error) bool Session::removeItem(const QString &itemPath, QString *error)
{ {
if (itemPath.isEmpty()) { if (itemPath.isEmpty())
{
if (error) if (error)
*error = tr("Cannot delete root folder."); *error = tr("Cannot delete root folder.");
return false; return false;
} }
auto item = m_itemsByPath.value(itemPath); auto item = m_itemsByPath.value(itemPath);
if (!item) { if (!item)
{
if (error) if (error)
*error = tr("Item doesn't exist: %1.").arg(itemPath); *error = tr("Item doesn't exist: %1.").arg(itemPath);
return false; return false;
@ -248,12 +255,14 @@ Item *Session::itemByPath(const QString &path) const
void Session::load() void Session::load()
{ {
QFile itemsFile(m_confFileStorage->storageDir().absoluteFilePath(FeedsFileName)); QFile itemsFile(m_confFileStorage->storageDir().absoluteFilePath(FeedsFileName));
if (!itemsFile.exists()) { if (!itemsFile.exists())
{
loadLegacy(); loadLegacy();
return; return;
} }
if (!itemsFile.open(QFile::ReadOnly)) { if (!itemsFile.open(QFile::ReadOnly))
{
Logger::instance()->addMessage( Logger::instance()->addMessage(
QString("Couldn't read RSS Session data from %1. Error: %2") QString("Couldn't read RSS Session data from %1. Error: %2")
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING); .arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
@ -262,14 +271,16 @@ void Session::load()
QJsonParseError jsonError; QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError); const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) { if (jsonError.error != QJsonParseError::NoError)
{
Logger::instance()->addMessage( Logger::instance()->addMessage(
QString("Couldn't parse RSS Session data from %1. Error: %2") QString("Couldn't parse RSS Session data from %1. Error: %2")
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING); .arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
return; return;
} }
if (!jsonDoc.isObject()) { if (!jsonDoc.isObject())
{
Logger::instance()->addMessage( Logger::instance()->addMessage(
QString("Couldn't load RSS Session data from %1. Invalid data format.") QString("Couldn't load RSS Session data from %1. Invalid data format.")
.arg(itemsFile.fileName()), Log::WARNING); .arg(itemsFile.fileName()), Log::WARNING);
@ -282,9 +293,11 @@ void Session::load()
void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder) void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
{ {
bool updated = false; bool updated = false;
for (const QString &key : asConst(jsonObj.keys())) { for (const QString &key : asConst(jsonObj.keys()))
{
const QJsonValue val {jsonObj[key]}; const QJsonValue val {jsonObj[key]};
if (val.isString()) { if (val.isString())
{
// previous format (reduced form) doesn't contain UID // previous format (reduced form) doesn't contain UID
QString url = val.toString(); QString url = val.toString();
if (url.isEmpty()) if (url.isEmpty())
@ -292,31 +305,38 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
addFeedToFolder(generateUID(), url, key, folder); addFeedToFolder(generateUID(), url, key, folder);
updated = true; updated = true;
} }
else if (val.isObject()) { else if (val.isObject())
{
const QJsonObject valObj {val.toObject()}; const QJsonObject valObj {val.toObject()};
if (valObj.contains("url")) { if (valObj.contains("url"))
if (!valObj["url"].isString()) { {
if (!valObj["url"].isString())
{
LogMsg(tr("Couldn't load RSS Feed '%1'. URL is required.") LogMsg(tr("Couldn't load RSS Feed '%1'. URL is required.")
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING); .arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
continue; continue;
} }
QUuid uid; QUuid uid;
if (valObj.contains("uid")) { if (valObj.contains("uid"))
{
uid = QUuid {valObj["uid"].toString()}; uid = QUuid {valObj["uid"].toString()};
if (uid.isNull()) { if (uid.isNull())
{
LogMsg(tr("Couldn't load RSS Feed '%1'. UID is invalid.") LogMsg(tr("Couldn't load RSS Feed '%1'. UID is invalid.")
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING); .arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
continue; continue;
} }
if (m_feedsByUID.contains(uid)) { if (m_feedsByUID.contains(uid))
{
LogMsg(tr("Duplicate RSS Feed UID: %1. Configuration seems to be corrupted.") LogMsg(tr("Duplicate RSS Feed UID: %1. Configuration seems to be corrupted.")
.arg(uid.toString()), Log::WARNING); .arg(uid.toString()), Log::WARNING);
continue; continue;
} }
} }
else { else
{
// previous format doesn't contain UID // previous format doesn't contain UID
uid = generateUID(); uid = generateUID();
updated = true; updated = true;
@ -324,11 +344,13 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
addFeedToFolder(uid, valObj["url"].toString(), key, folder); addFeedToFolder(uid, valObj["url"].toString(), key, folder);
} }
else { else
{
loadFolder(valObj, addSubfolder(key, folder)); loadFolder(valObj, addSubfolder(key, folder));
} }
} }
else { else
{
LogMsg(tr("Couldn't load RSS Item '%1'. Invalid data format.") LogMsg(tr("Couldn't load RSS Item '%1'. Invalid data format.")
.arg(QString::fromLatin1("%1\\%2").arg(folder->path(), key)), Log::WARNING); .arg(QString::fromLatin1("%1\\%2").arg(folder->path(), key)), Log::WARNING);
} }
@ -342,13 +364,15 @@ void Session::loadLegacy()
{ {
const QStringList legacyFeedPaths = SettingsStorage::instance()->loadValue("Rss/streamList").toStringList(); const QStringList legacyFeedPaths = SettingsStorage::instance()->loadValue("Rss/streamList").toStringList();
const QStringList feedAliases = SettingsStorage::instance()->loadValue("Rss/streamAlias").toStringList(); const QStringList feedAliases = SettingsStorage::instance()->loadValue("Rss/streamAlias").toStringList();
if (legacyFeedPaths.size() != feedAliases.size()) { if (legacyFeedPaths.size() != feedAliases.size())
{
Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING); Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING);
return; return;
} }
uint i = 0; uint i = 0;
for (QString legacyPath : legacyFeedPaths) { for (QString legacyPath : legacyFeedPaths)
{
if (Item::PathSeparator == QString(legacyPath[0])) if (Item::PathSeparator == QString(legacyPath[0]))
legacyPath.remove(0, 1); legacyPath.remove(0, 1);
const QString parentFolderPath = Item::parentPath(legacyPath); const QString parentFolderPath = Item::parentPath(legacyPath);
@ -374,13 +398,15 @@ void Session::store()
Folder *Session::prepareItemDest(const QString &path, QString *error) Folder *Session::prepareItemDest(const QString &path, QString *error)
{ {
if (!Item::isValidPath(path)) { if (!Item::isValidPath(path))
{
if (error) if (error)
*error = tr("Incorrect RSS Item path: %1.").arg(path); *error = tr("Incorrect RSS Item path: %1.").arg(path);
return nullptr; return nullptr;
} }
if (m_itemsByPath.contains(path)) { if (m_itemsByPath.contains(path))
{
if (error) if (error)
*error = tr("RSS item with given path already exists: %1.").arg(path); *error = tr("RSS item with given path already exists: %1.").arg(path);
return nullptr; return nullptr;
@ -388,7 +414,8 @@ Folder *Session::prepareItemDest(const QString &path, QString *error)
const QString destFolderPath = Item::parentPath(path); const QString destFolderPath = Item::parentPath(path);
auto destFolder = qobject_cast<Folder *>(m_itemsByPath.value(destFolderPath)); auto destFolder = qobject_cast<Folder *>(m_itemsByPath.value(destFolderPath));
if (!destFolder) { if (!destFolder)
{
if (error) if (error)
*error = tr("Parent folder doesn't exist: %1.").arg(destFolderPath); *error = tr("Parent folder doesn't exist: %1.").arg(destFolderPath);
return nullptr; return nullptr;
@ -413,7 +440,8 @@ Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QStri
void Session::addItem(Item *item, Folder *destFolder) void Session::addItem(Item *item, Folder *destFolder)
{ {
if (auto feed = qobject_cast<Feed *>(item)) { if (auto feed = qobject_cast<Feed *>(item))
{
connect(feed, &Feed::titleChanged, this, &Session::handleFeedTitleChanged); connect(feed, &Feed::titleChanged, this, &Session::handleFeedTitleChanged);
connect(feed, &Feed::iconLoaded, this, &Session::feedIconLoaded); connect(feed, &Feed::iconLoaded, this, &Session::feedIconLoaded);
connect(feed, &Feed::stateChanged, this, &Session::feedStateChanged); connect(feed, &Feed::stateChanged, this, &Session::feedStateChanged);
@ -435,14 +463,17 @@ bool Session::isProcessingEnabled() const
void Session::setProcessingEnabled(bool enabled) void Session::setProcessingEnabled(bool enabled)
{ {
if (m_processingEnabled != enabled) { if (m_processingEnabled != enabled)
{
m_processingEnabled = enabled; m_processingEnabled = enabled;
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled); SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
if (m_processingEnabled) { if (m_processingEnabled)
{
m_refreshTimer.start(m_refreshInterval * MsecsPerMin); m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
refresh(); refresh();
} }
else { else
{
m_refreshTimer.stop(); m_refreshTimer.stop();
} }
@ -482,7 +513,8 @@ int Session::refreshInterval() const
void Session::setRefreshInterval(const int refreshInterval) void Session::setRefreshInterval(const int refreshInterval)
{ {
if (m_refreshInterval != refreshInterval) { if (m_refreshInterval != refreshInterval)
{
SettingsStorage::instance()->storeValue(SettingsKey_RefreshInterval, refreshInterval); SettingsStorage::instance()->storeValue(SettingsKey_RefreshInterval, refreshInterval);
m_refreshInterval = refreshInterval; m_refreshInterval = refreshInterval;
m_refreshTimer.start(m_refreshInterval * MsecsPerMin); m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
@ -498,7 +530,8 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
{ {
m_itemsByPath.remove(item->path()); m_itemsByPath.remove(item->path());
auto feed = qobject_cast<Feed *>(item); auto feed = qobject_cast<Feed *>(item);
if (feed) { if (feed)
{
m_feedsByUID.remove(feed->uid()); m_feedsByUID.remove(feed->uid());
m_feedsByURL.remove(feed->url()); m_feedsByURL.remove(feed->url());
} }
@ -528,7 +561,8 @@ int Session::maxArticlesPerFeed() const
void Session::setMaxArticlesPerFeed(const int n) void Session::setMaxArticlesPerFeed(const int n)
{ {
if (m_maxArticlesPerFeed != n) { if (m_maxArticlesPerFeed != n)
{
m_maxArticlesPerFeed = n; m_maxArticlesPerFeed = n;
SettingsStorage::instance()->storeValue(SettingsKey_MaxArticlesPerFeed, n); SettingsStorage::instance()->storeValue(SettingsKey_MaxArticlesPerFeed, n);
emit maxArticlesPerFeedChanged(n); emit maxArticlesPerFeedChanged(n);

View file

@ -34,20 +34,26 @@
* RSS Session configuration file format (JSON): * RSS Session configuration file format (JSON):
* *
* =============== BEGIN =============== * =============== BEGIN ===============
* { *
* "folder1": { {
* "subfolder1": { * "folder1":
* "Feed name 1 (Alias)": { {
* "subfolder1":
{
* "Feed name 1 (Alias)":
{
* "uid": "feed unique identifier", * "uid": "feed unique identifier",
* "url": "http://some-feed-url1" * "url": "http://some-feed-url1"
* } * }
* "Feed name 2 (Alias)": { * "Feed name 2 (Alias)":
{
* "uid": "feed unique identifier", * "uid": "feed unique identifier",
* "url": "http://some-feed-url2" * "url": "http://some-feed-url2"
* } * }
* }, * },
* "subfolder2": {}, * "subfolder2": {},
* "Feed name 3 (Alias)": { * "Feed name 3 (Alias)":
{
* "uid": "feed unique identifier", * "uid": "feed unique identifier",
* "url": "http://some-feed-url3" * "url": "http://some-feed-url3"
* } * }

View file

@ -106,17 +106,21 @@ QVariant ScanFoldersModel::data(const QModelIndex &index, int role) const
const PathData *pathData = m_pathList.at(index.row()); const PathData *pathData = m_pathList.at(index.row());
QVariant value; QVariant value;
switch (index.column()) { switch (index.column())
{
case WATCH: case WATCH:
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
value = Utils::Fs::toNativePath(pathData->watchPath); value = Utils::Fs::toNativePath(pathData->watchPath);
break; break;
case DOWNLOAD: case DOWNLOAD:
if (role == Qt::UserRole) { if (role == Qt::UserRole)
{
value = pathData->downloadType; value = pathData->downloadType;
} }
else if (role == Qt::DisplayRole) { else if (role == Qt::DisplayRole)
switch (pathData->downloadType) { {
switch (pathData->downloadType)
{
case DOWNLOAD_IN_WATCH_FOLDER: case DOWNLOAD_IN_WATCH_FOLDER:
case DEFAULT_LOCATION: case DEFAULT_LOCATION:
value = pathTypeDisplayName(pathData->downloadType); value = pathTypeDisplayName(pathData->downloadType);
@ -139,7 +143,8 @@ QVariant ScanFoldersModel::headerData(int section, Qt::Orientation orientation,
QVariant title; QVariant title;
switch (section) { switch (section)
{
case WATCH: case WATCH:
title = tr("Monitored Folder"); title = tr("Monitored Folder");
break; break;
@ -158,7 +163,8 @@ Qt::ItemFlags ScanFoldersModel::flags(const QModelIndex &index) const
Qt::ItemFlags flags; Qt::ItemFlags flags;
switch (index.column()) { switch (index.column())
{
case WATCH: case WATCH:
flags = QAbstractListModel::flags(index); flags = QAbstractListModel::flags(index);
break; break;
@ -176,7 +182,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
|| (index.column() != DOWNLOAD)) || (index.column() != DOWNLOAD))
return false; return false;
if (role == Qt::UserRole) { if (role == Qt::UserRole)
{
const auto type = static_cast<PathType>(value.toInt()); const auto type = static_cast<PathType>(value.toInt());
if (type == CUSTOM_LOCATION) if (type == CUSTOM_LOCATION)
return false; return false;
@ -185,7 +192,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
m_pathList[index.row()]->downloadPath.clear(); m_pathList[index.row()]->downloadPath.clear();
emit dataChanged(index, index); emit dataChanged(index, index);
} }
else if (role == Qt::DisplayRole) { else if (role == Qt::DisplayRole)
{
const QString path = value.toString(); const QString path = value.toString();
if (path.isEmpty()) // means we didn't pass CUSTOM_LOCATION type if (path.isEmpty()) // means we didn't pass CUSTOM_LOCATION type
return false; return false;
@ -194,7 +202,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
m_pathList[index.row()]->downloadPath = Utils::Fs::toNativePath(path); m_pathList[index.row()]->downloadPath = Utils::Fs::toNativePath(path);
emit dataChanged(index, index); emit dataChanged(index, index);
} }
else { else
{
return false; return false;
} }
@ -213,7 +222,8 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
const QDir downloadDir(downloadPath); const QDir downloadDir(downloadPath);
const QString canonicalDownloadPath = downloadDir.canonicalPath(); const QString canonicalDownloadPath = downloadDir.canonicalPath();
if (!m_fsWatcher) { if (!m_fsWatcher)
{
m_fsWatcher = new FileSystemWatcher(this); m_fsWatcher = new FileSystemWatcher(this);
connect(m_fsWatcher, &FileSystemWatcher::torrentsAdded, this, &ScanFoldersModel::addTorrentsToSession); connect(m_fsWatcher, &FileSystemWatcher::torrentsAdded, this, &ScanFoldersModel::addTorrentsToSession);
} }
@ -249,7 +259,8 @@ void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
if (!m_fsWatcher) if (!m_fsWatcher)
return; // addPath() wasn't called before this return; // addPath() wasn't called before this
for (const QString &path : watchPaths) { for (const QString &path : watchPaths)
{
const QDir watchDir(path); const QDir watchDir(path);
const QString canonicalWatchPath = watchDir.canonicalPath(); const QString canonicalWatchPath = watchDir.canonicalPath();
m_fsWatcher->addPath(canonicalWatchPath); m_fsWatcher->addPath(canonicalWatchPath);
@ -321,7 +332,8 @@ void ScanFoldersModel::makePersistent()
{ {
QVariantHash dirs; QVariantHash dirs;
for (const PathData *pathData : asConst(m_pathList)) { for (const PathData *pathData : asConst(m_pathList))
{
if (pathData->downloadType == CUSTOM_LOCATION) if (pathData->downloadType == CUSTOM_LOCATION)
dirs.insert(Utils::Fs::toUniformPath(pathData->watchPath), Utils::Fs::toUniformPath(pathData->downloadPath)); dirs.insert(Utils::Fs::toUniformPath(pathData->watchPath), Utils::Fs::toUniformPath(pathData->downloadPath));
else else
@ -335,7 +347,8 @@ void ScanFoldersModel::configure()
{ {
const QVariantHash dirs = Preferences::instance()->getScanDirs(); const QVariantHash dirs = Preferences::instance()->getScanDirs();
for (auto i = dirs.cbegin(); i != dirs.cend(); ++i) { for (auto i = dirs.cbegin(); i != dirs.cend(); ++i)
{
if (i.value().type() == QVariant::Int) if (i.value().type() == QVariant::Int)
addPath(i.key(), static_cast<PathType>(i.value().toInt()), QString()); addPath(i.key(), static_cast<PathType>(i.value().toInt()), QString());
else else
@ -345,22 +358,27 @@ void ScanFoldersModel::configure()
void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList) void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
{ {
for (const QString &file : pathList) { for (const QString &file : pathList)
{
qDebug("File %s added", qUtf8Printable(file)); qDebug("File %s added", qUtf8Printable(file));
BitTorrent::AddTorrentParams params; BitTorrent::AddTorrentParams params;
if (downloadInWatchFolder(file)) { if (downloadInWatchFolder(file))
{
params.savePath = QFileInfo(file).dir().path(); params.savePath = QFileInfo(file).dir().path();
params.useAutoTMM = TriStateBool::False; params.useAutoTMM = TriStateBool::False;
} }
else if (!downloadInDefaultFolder(file)) { else if (!downloadInDefaultFolder(file))
{
params.savePath = downloadPathTorrentFolder(file); params.savePath = downloadPathTorrentFolder(file);
params.useAutoTMM = TriStateBool::False; params.useAutoTMM = TriStateBool::False;
} }
if (file.endsWith(".magnet", Qt::CaseInsensitive)) { if (file.endsWith(".magnet", Qt::CaseInsensitive))
{
QFile f(file); QFile f(file);
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { if (f.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream str(&f); QTextStream str(&f);
while (!str.atEnd()) while (!str.atEnd())
BitTorrent::Session::instance()->addTorrent(str.readLine(), params); BitTorrent::Session::instance()->addTorrent(str.readLine(), params);
@ -368,17 +386,21 @@ void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
f.close(); f.close();
Utils::Fs::forceRemove(file); Utils::Fs::forceRemove(file);
} }
else { else
{
qDebug("Failed to open magnet file: %s", qUtf8Printable(f.errorString())); qDebug("Failed to open magnet file: %s", qUtf8Printable(f.errorString()));
} }
} }
else { else
{
const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::loadFromFile(file); const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::loadFromFile(file);
if (torrentInfo.isValid()) { if (torrentInfo.isValid())
{
BitTorrent::Session::instance()->addTorrent(torrentInfo, params); BitTorrent::Session::instance()->addTorrent(torrentInfo, params);
Utils::Fs::forceRemove(file); Utils::Fs::forceRemove(file);
} }
else { else
{
qDebug("Ignoring incomplete torrent file: %s", qUtf8Printable(file)); qDebug("Ignoring incomplete torrent file: %s", qUtf8Printable(file));
} }
} }
@ -387,7 +409,8 @@ void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
QString ScanFoldersModel::pathTypeDisplayName(const PathType type) QString ScanFoldersModel::pathTypeDisplayName(const PathType type)
{ {
switch (type) { switch (type)
{
case DOWNLOAD_IN_WATCH_FOLDER: case DOWNLOAD_IN_WATCH_FOLDER:
return tr("Monitored folder"); return tr("Monitored folder");
case DEFAULT_LOCATION: case DEFAULT_LOCATION:

View file

@ -42,7 +42,8 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
m_downloadProcess->setEnvironment(QProcess::systemEnvironment()); m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished) connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
, this, &SearchDownloadHandler::downloadProcessFinished); , this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params { const QStringList params
{
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"), Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"),
siteUrl, siteUrl,
url url
@ -55,7 +56,8 @@ void SearchDownloadHandler::downloadProcessFinished(int exitcode)
{ {
QString path; QString path;
if ((exitcode == 0) && (m_downloadProcess->exitStatus() == QProcess::NormalExit)) { if ((exitcode == 0) && (m_downloadProcess->exitStatus() == QProcess::NormalExit))
{
const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed(); const QString line = QString::fromUtf8(m_downloadProcess->readAllStandardOutput()).trimmed();
const QVector<QStringRef> parts = line.splitRef(' '); const QVector<QStringRef> parts = line.splitRef(' ');
if (parts.size() == 2) if (parts.size() == 2)

View file

@ -64,7 +64,8 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
// Load environment variables (proxy) // Load environment variables (proxy)
m_searchProcess->setEnvironment(QProcess::systemEnvironment()); m_searchProcess->setEnvironment(QProcess::systemEnvironment());
const QStringList params { const QStringList params
{
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"), Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
m_usedPlugins.join(','), m_usedPlugins.join(','),
m_category m_category
@ -137,13 +138,15 @@ void SearchHandler::readSearchOutput()
QVector<SearchResult> searchResultList; QVector<SearchResult> searchResultList;
searchResultList.reserve(lines.size()); searchResultList.reserve(lines.size());
for (const QByteArray &line : asConst(lines)) { for (const QByteArray &line : asConst(lines))
{
SearchResult searchResult; SearchResult searchResult;
if (parseSearchResult(QString::fromUtf8(line), searchResult)) if (parseSearchResult(QString::fromUtf8(line), searchResult))
searchResultList << searchResult; searchResultList << searchResult;
} }
if (!searchResultList.isEmpty()) { if (!searchResultList.isEmpty())
{
for (const SearchResult &result : searchResultList) for (const SearchResult &result : searchResultList)
m_results.append(result); m_results.append(result);
emit newSearchResults(searchResultList); emit newSearchResults(searchResultList);

View file

@ -61,16 +61,19 @@ namespace
while (iter.hasNext()) while (iter.hasNext())
dirs += iter.next(); dirs += iter.next();
for (const QString &dir : asConst(dirs)) { for (const QString &dir : asConst(dirs))
{
// python 3: remove "__pycache__" folders // python 3: remove "__pycache__" folders
if (dir.endsWith("/__pycache__")) { if (dir.endsWith("/__pycache__"))
{
Utils::Fs::removeDirRecursive(dir); Utils::Fs::removeDirRecursive(dir);
continue; continue;
} }
// python 2: remove "*.pyc" files // python 2: remove "*.pyc" files
const QStringList files = QDir(dir).entryList(QDir::Files); const QStringList files = QDir(dir).entryList(QDir::Files);
for (const QString &file : files) { for (const QString &file : files)
{
if (file.endsWith(".pyc")) if (file.endsWith(".pyc"))
Utils::Fs::forceRemove(file); Utils::Fs::forceRemove(file);
} }
@ -115,7 +118,8 @@ QStringList SearchPluginManager::allPlugins() const
QStringList SearchPluginManager::enabledPlugins() const QStringList SearchPluginManager::enabledPlugins() const
{ {
QStringList plugins; QStringList plugins;
for (const PluginInfo *plugin : asConst(m_plugins)) { for (const PluginInfo *plugin : asConst(m_plugins))
{
if (plugin->enabled) if (plugin->enabled)
plugins << plugin->name; plugins << plugin->name;
} }
@ -126,9 +130,12 @@ QStringList SearchPluginManager::enabledPlugins() const
QStringList SearchPluginManager::supportedCategories() const QStringList SearchPluginManager::supportedCategories() const
{ {
QStringList result; QStringList result;
for (const PluginInfo *plugin : asConst(m_plugins)) { for (const PluginInfo *plugin : asConst(m_plugins))
if (plugin->enabled) { {
for (const QString &cat : plugin->supportedCategories) { if (plugin->enabled)
{
for (const QString &cat : plugin->supportedCategories)
{
if (!result.contains(cat)) if (!result.contains(cat))
result << cat; result << cat;
} }
@ -149,7 +156,8 @@ QStringList SearchPluginManager::getPluginCategories(const QString &pluginName)
plugins << pluginName.trimmed(); plugins << pluginName.trimmed();
QSet<QString> categories; QSet<QString> categories;
for (const QString &name : asConst(plugins)) { for (const QString &name : asConst(plugins))
{
const PluginInfo *plugin = pluginInfo(name); const PluginInfo *plugin = pluginInfo(name);
if (!plugin) continue; // plugin wasn't found if (!plugin) continue; // plugin wasn't found
for (const QString &category : plugin->supportedCategories) for (const QString &category : plugin->supportedCategories)
@ -167,7 +175,8 @@ PluginInfo *SearchPluginManager::pluginInfo(const QString &name) const
void SearchPluginManager::enablePlugin(const QString &name, const bool enabled) void SearchPluginManager::enablePlugin(const QString &name, const bool enabled)
{ {
PluginInfo *plugin = m_plugins.value(name, nullptr); PluginInfo *plugin = m_plugins.value(name, nullptr);
if (plugin) { if (plugin)
{
plugin->enabled = enabled; plugin->enabled = enabled;
// Save to Hard disk // Save to Hard disk
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
@ -193,12 +202,14 @@ void SearchPluginManager::installPlugin(const QString &source)
{ {
clearPythonCache(engineLocation()); clearPythonCache(engineLocation());
if (Net::DownloadManager::hasSupportedScheme(source)) { if (Net::DownloadManager::hasSupportedScheme(source))
{
using namespace Net; using namespace Net;
DownloadManager::instance()->download(DownloadRequest(source).saveToFile(true) DownloadManager::instance()->download(DownloadRequest(source).saveToFile(true)
, this, &SearchPluginManager::pluginDownloadFinished); , this, &SearchPluginManager::pluginDownloadFinished);
} }
else { else
{
QString path = source; QString path = source;
if (path.startsWith("file:", Qt::CaseInsensitive)) if (path.startsWith("file:", Qt::CaseInsensitive))
path = QUrl(path).toLocalFile(); path = QUrl(path).toLocalFile();
@ -217,7 +228,8 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
{ {
const PluginVersion newVersion = getPluginVersion(path); const PluginVersion newVersion = getPluginVersion(path);
const PluginInfo *plugin = pluginInfo(name); const PluginInfo *plugin = pluginInfo(name);
if (plugin && !(plugin->version < newVersion)) { if (plugin && !(plugin->version < newVersion))
{
LogMsg(tr("Plugin already at version %1, which is greater than %2").arg(plugin->version, newVersion), Log::INFO); LogMsg(tr("Plugin already at version %1, which is greater than %2").arg(plugin->version, newVersion), Log::INFO);
emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed.")); emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed."));
return; return;
@ -226,7 +238,8 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
// Process with install // Process with install
const QString destPath = pluginPath(name); const QString destPath = pluginPath(name);
bool updated = false; bool updated = false;
if (QFile::exists(destPath)) { if (QFile::exists(destPath))
{
// Backup in case install fails // Backup in case install fails
QFile::copy(destPath, destPath + ".bak"); QFile::copy(destPath, destPath + ".bak");
Utils::Fs::forceRemove(destPath); Utils::Fs::forceRemove(destPath);
@ -237,11 +250,13 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
// Update supported plugins // Update supported plugins
update(); update();
// Check if this was correctly installed // Check if this was correctly installed
if (!m_plugins.contains(name)) { if (!m_plugins.contains(name))
{
// Remove broken file // Remove broken file
Utils::Fs::forceRemove(destPath); Utils::Fs::forceRemove(destPath);
LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO); LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO);
if (updated) { if (updated)
{
// restore backup // restore backup
QFile::copy(destPath + ".bak", destPath); QFile::copy(destPath + ".bak", destPath);
Utils::Fs::forceRemove(destPath + ".bak"); Utils::Fs::forceRemove(destPath + ".bak");
@ -249,13 +264,16 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
update(); update();
emit pluginUpdateFailed(name, tr("Plugin is not supported.")); emit pluginUpdateFailed(name, tr("Plugin is not supported."));
} }
else { else
{
emit pluginInstallationFailed(name, tr("Plugin is not supported.")); emit pluginInstallationFailed(name, tr("Plugin is not supported."));
} }
} }
else { else
{
// Install was successful, remove backup // Install was successful, remove backup
if (updated) { if (updated)
{
LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO); LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO);
Utils::Fs::forceRemove(destPath + ".bak"); Utils::Fs::forceRemove(destPath + ".bak");
} }
@ -284,10 +302,12 @@ void SearchPluginManager::updateIconPath(PluginInfo *const plugin)
{ {
if (!plugin) return; if (!plugin) return;
QString iconPath = QString::fromLatin1("%1/%2.png").arg(pluginsLocation(), plugin->name); QString iconPath = QString::fromLatin1("%1/%2.png").arg(pluginsLocation(), plugin->name);
if (QFile::exists(iconPath)) { if (QFile::exists(iconPath))
{
plugin->iconPath = iconPath; plugin->iconPath = iconPath;
} }
else { else
{
iconPath = QString::fromLatin1("%1/%2.ico").arg(pluginsLocation(), plugin->name); iconPath = QString::fromLatin1("%1/%2.ico").arg(pluginsLocation(), plugin->name);
if (QFile::exists(iconPath)) if (QFile::exists(iconPath))
plugin->iconPath = iconPath; plugin->iconPath = iconPath;
@ -317,7 +337,8 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
QString SearchPluginManager::categoryFullName(const QString &categoryName) QString SearchPluginManager::categoryFullName(const QString &categoryName)
{ {
const QHash<QString, QString> categoryTable { const QHash<QString, QString> categoryTable
{
{"all", tr("All categories")}, {"all", tr("All categories")},
{"movies", tr("Movies")}, {"movies", tr("Movies")},
{"tv", tr("TV shows")}, {"tv", tr("TV shows")},
@ -344,7 +365,8 @@ QString SearchPluginManager::pluginsLocation()
QString SearchPluginManager::engineLocation() QString SearchPluginManager::engineLocation()
{ {
static QString location; static QString location;
if (location.isEmpty()) { if (location.isEmpty())
{
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "nova3"); location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "nova3");
const QDir locationDir(location); const QDir locationDir(location);
@ -364,7 +386,8 @@ void SearchPluginManager::versionInfoDownloadFinished(const Net::DownloadResult
void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &result) void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &result)
{ {
if (result.status == Net::DownloadStatus::Success) { if (result.status == Net::DownloadStatus::Success)
{
const QString filePath = Utils::Fs::toUniformPath(result.filePath); const QString filePath = Utils::Fs::toUniformPath(result.filePath);
QString pluginName = Utils::Fs::fileName(result.url); QString pluginName = Utils::Fs::fileName(result.url);
@ -372,7 +395,8 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
installPlugin_impl(pluginName, filePath); installPlugin_impl(pluginName, filePath);
Utils::Fs::forceRemove(filePath); Utils::Fs::forceRemove(filePath);
} }
else { else
{
const QString url = result.url; const QString url = result.url;
QString pluginName = url.mid(url.lastIndexOf('/') + 1); QString pluginName = url.mid(url.lastIndexOf('/') + 1);
pluginName.replace(".py", "", Qt::CaseInsensitive); pluginName.replace(".py", "", Qt::CaseInsensitive);
@ -432,21 +456,25 @@ void SearchPluginManager::update()
const QString capabilities = nova.readAll(); const QString capabilities = nova.readAll();
QDomDocument xmlDoc; QDomDocument xmlDoc;
if (!xmlDoc.setContent(capabilities)) { if (!xmlDoc.setContent(capabilities))
{
qWarning() << "Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data(); qWarning() << "Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
qWarning() << "Error: " << nova.readAllStandardError().constData(); qWarning() << "Error: " << nova.readAllStandardError().constData();
return; return;
} }
const QDomElement root = xmlDoc.documentElement(); const QDomElement root = xmlDoc.documentElement();
if (root.tagName() != "capabilities") { if (root.tagName() != "capabilities")
{
qWarning() << "Invalid XML file for Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data(); qWarning() << "Invalid XML file for Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
return; return;
} }
for (QDomNode engineNode = root.firstChild(); !engineNode.isNull(); engineNode = engineNode.nextSibling()) { for (QDomNode engineNode = root.firstChild(); !engineNode.isNull(); engineNode = engineNode.nextSibling())
{
const QDomElement engineElem = engineNode.toElement(); const QDomElement engineElem = engineNode.toElement();
if (!engineElem.isNull()) { if (!engineElem.isNull())
{
const QString pluginName = engineElem.tagName(); const QString pluginName = engineElem.tagName();
auto plugin = std::make_unique<PluginInfo>(); auto plugin = std::make_unique<PluginInfo>();
@ -456,7 +484,8 @@ void SearchPluginManager::update()
plugin->url = engineElem.elementsByTagName("url").at(0).toElement().text(); plugin->url = engineElem.elementsByTagName("url").at(0).toElement().text();
const QStringList categories = engineElem.elementsByTagName("categories").at(0).toElement().text().split(' '); const QStringList categories = engineElem.elementsByTagName("categories").at(0).toElement().text().split(' ');
for (QString cat : categories) { for (QString cat : categories)
{
cat = cat.trimmed(); cat = cat.trimmed();
if (!cat.isEmpty()) if (!cat.isEmpty())
plugin->supportedCategories << cat; plugin->supportedCategories << cat;
@ -467,11 +496,13 @@ void SearchPluginManager::update()
updateIconPath(plugin.get()); updateIconPath(plugin.get());
if (!m_plugins.contains(pluginName)) { if (!m_plugins.contains(pluginName))
{
m_plugins[pluginName] = plugin.release(); m_plugins[pluginName] = plugin.release();
emit pluginInstalled(pluginName); emit pluginInstalled(pluginName);
} }
else if (m_plugins[pluginName]->version != plugin->version) { else if (m_plugins[pluginName]->version != plugin->version)
{
delete m_plugins.take(pluginName); delete m_plugins.take(pluginName);
m_plugins[pluginName] = plugin.release(); m_plugins[pluginName] = plugin.release();
emit pluginUpdated(pluginName); emit pluginUpdated(pluginName);
@ -486,7 +517,8 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
int numCorrectData = 0; int numCorrectData = 0;
const QVector<QByteArray> lines = Utils::ByteArray::splitToViews(info, "\n", QString::SkipEmptyParts); const QVector<QByteArray> lines = Utils::ByteArray::splitToViews(info, "\n", QString::SkipEmptyParts);
for (QByteArray line : lines) { for (QByteArray line : lines)
{
line = line.trimmed(); line = line.trimmed();
if (line.isEmpty()) continue; if (line.isEmpty()) continue;
if (line.startsWith('#')) continue; if (line.startsWith('#')) continue;
@ -500,17 +532,20 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
if (!version.isValid()) continue; if (!version.isValid()) continue;
++numCorrectData; ++numCorrectData;
if (isUpdateNeeded(pluginName, version)) { if (isUpdateNeeded(pluginName, version))
{
LogMsg(tr("Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version), Log::INFO); LogMsg(tr("Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version), Log::INFO);
updateInfo[pluginName] = version; updateInfo[pluginName] = version;
} }
} }
if (numCorrectData < lines.size()) { if (numCorrectData < lines.size())
{
emit checkForUpdatesFailed(tr("Incorrect update info received for %1 out of %2 plugins.") emit checkForUpdatesFailed(tr("Incorrect update info received for %1 out of %2 plugins.")
.arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size()))); .arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size())));
} }
else { else
{
emit checkForUpdatesFinished(updateInfo); emit checkForUpdatesFinished(updateInfo);
} }
} }
@ -535,7 +570,8 @@ PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath)
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text)) if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
return {}; return {};
while (!pluginFile.atEnd()) { while (!pluginFile.atEnd())
{
const QString line = QString(pluginFile.readLine()).remove(' '); const QString line = QString(pluginFile.readLine()).remove(' ');
if (!line.startsWith("#VERSION:", Qt::CaseInsensitive)) continue; if (!line.startsWith("#VERSION:", Qt::CaseInsensitive)) continue;

View file

@ -67,7 +67,8 @@ namespace
QString mapKey(const QString &key) QString mapKey(const QString &key)
{ {
static const QHash<QString, QString> keyMapping = { static const QHash<QString, QString> keyMapping =
{
{"BitTorrent/Session/MaxRatioAction", "Preferences/Bittorrent/MaxRatioAction"}, {"BitTorrent/Session/MaxRatioAction", "Preferences/Bittorrent/MaxRatioAction"},
{"BitTorrent/Session/DefaultSavePath", "Preferences/Downloads/SavePath"}, {"BitTorrent/Session/DefaultSavePath", "Preferences/Downloads/SavePath"},
{"BitTorrent/Session/TempPath", "Preferences/Downloads/TempPath"}, {"BitTorrent/Session/TempPath", "Preferences/Downloads/TempPath"},
@ -189,7 +190,8 @@ bool SettingsStorage::save()
if (!m_dirty) return true; // something might have changed while we were getting the lock if (!m_dirty) return true; // something might have changed while we were getting the lock
const TransactionalSettings settings(QLatin1String("qBittorrent")); const TransactionalSettings settings(QLatin1String("qBittorrent"));
if (!settings.write(m_data)) { if (!settings.write(m_data))
{
m_timer.start(); m_timer.start();
return false; return false;
} }
@ -211,7 +213,8 @@ void SettingsStorage::storeValue(const QString &key, const QVariant &value)
const QWriteLocker locker(&m_lock); const QWriteLocker locker(&m_lock);
QVariant &currentValue = m_data[realKey]; QVariant &currentValue = m_data[realKey];
if (currentValue != value) { if (currentValue != value)
{
m_dirty = true; m_dirty = true;
currentValue = value; currentValue = value;
m_timer.start(); m_timer.start();
@ -222,7 +225,8 @@ void SettingsStorage::removeValue(const QString &key)
{ {
const QString realKey = mapKey(key); const QString realKey = mapKey(key);
const QWriteLocker locker(&m_lock); const QWriteLocker locker(&m_lock);
if (m_data.remove(realKey) > 0) { if (m_data.remove(realKey) > 0)
{
m_dirty = true; m_dirty = true;
m_timer.start(); m_timer.start();
} }
@ -233,7 +237,8 @@ QVariantHash TransactionalSettings::read() const
QVariantHash res; QVariantHash res;
const QString newPath = deserialize(m_name + QLatin1String("_new"), res); const QString newPath = deserialize(m_name + QLatin1String("_new"), res);
if (!newPath.isEmpty()) { // "_new" file is NOT empty if (!newPath.isEmpty())
{ // "_new" file is NOT empty
// This means that the PC closed either due to power outage // This means that the PC closed either due to power outage
// or because the disk was full. In any case the settings weren't transferred // or because the disk was full. In any case the settings weren't transferred
// in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf // in their final position. So assume that qbittorrent_new.ini/qbittorrent_new.conf
@ -249,7 +254,8 @@ QVariantHash TransactionalSettings::read() const
Utils::Fs::forceRemove(finalPath); Utils::Fs::forceRemove(finalPath);
QFile::rename(newPath, finalPath); QFile::rename(newPath, finalPath);
} }
else { else
{
deserialize(m_name, res); deserialize(m_name, res);
} }
@ -264,7 +270,8 @@ bool TransactionalSettings::write(const QVariantHash &data) const
// Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds // Write everything to qBittorrent_new.ini/qBittorrent_new.conf and if it succeeds
// replace qBittorrent.ini/qBittorrent.conf with it. // replace qBittorrent.ini/qBittorrent.conf with it.
const QString newPath = serialize(m_name + QLatin1String("_new"), data); const QString newPath = serialize(m_name + QLatin1String("_new"), data);
if (newPath.isEmpty()) { if (newPath.isEmpty())
{
Utils::Fs::forceRemove(newPath); Utils::Fs::forceRemove(newPath);
return false; return false;
} }
@ -301,7 +308,8 @@ QString TransactionalSettings::serialize(const QString &name, const QVariantHash
settings->sync(); // Important to get error status settings->sync(); // Important to get error status
switch (settings->status()) { switch (settings->status())
{
case QSettings::NoError: case QSettings::NoError:
return settings->fileName(); return settings->fileName();
case QSettings::AccessError: case QSettings::AccessError:

View file

@ -73,7 +73,8 @@ TorrentFilter::TorrentFilter(const QString &filter, const QStringSet &hashSet, c
bool TorrentFilter::setType(Type type) bool TorrentFilter::setType(Type type)
{ {
if (m_type != type) { if (m_type != type)
{
m_type = type; m_type = type;
return true; return true;
} }
@ -113,7 +114,8 @@ bool TorrentFilter::setTypeByName(const QString &filter)
bool TorrentFilter::setHashSet(const QStringSet &hashSet) bool TorrentFilter::setHashSet(const QStringSet &hashSet)
{ {
if (m_hashSet != hashSet) { if (m_hashSet != hashSet)
{
m_hashSet = hashSet; m_hashSet = hashSet;
return true; return true;
} }
@ -126,7 +128,8 @@ bool TorrentFilter::setCategory(const QString &category)
// QString::operator==() doesn't distinguish between empty and null strings. // QString::operator==() doesn't distinguish between empty and null strings.
if ((m_category != category) if ((m_category != category)
|| (m_category.isNull() && !category.isNull()) || (m_category.isNull() && !category.isNull())
|| (!m_category.isNull() && category.isNull())) { || (!m_category.isNull() && category.isNull()))
{
m_category = category; m_category = category;
return true; return true;
} }
@ -139,7 +142,8 @@ bool TorrentFilter::setTag(const QString &tag)
// QString::operator==() doesn't distinguish between empty and null strings. // QString::operator==() doesn't distinguish between empty and null strings.
if ((m_tag != tag) if ((m_tag != tag)
|| (m_tag.isNull() && !tag.isNull()) || (m_tag.isNull() && !tag.isNull())
|| (!m_tag.isNull() && tag.isNull())) { || (!m_tag.isNull() && tag.isNull()))
{
m_tag = tag; m_tag = tag;
return true; return true;
} }
@ -156,7 +160,8 @@ bool TorrentFilter::match(const TorrentHandle *const torrent) const
bool TorrentFilter::matchState(const BitTorrent::TorrentHandle *const torrent) const bool TorrentFilter::matchState(const BitTorrent::TorrentHandle *const torrent) const
{ {
switch (m_type) { switch (m_type)
{
case All: case All:
return true; return true;
case Downloading: case Downloading:

View file

@ -40,7 +40,8 @@ QVector<QByteArray> Utils::ByteArray::splitToViews(const QByteArray &in, const Q
? (1 + (in.size() / sep.size())) ? (1 + (in.size() / sep.size()))
: (1 + (in.size() / (sep.size() + 1)))); : (1 + (in.size() / (sep.size() + 1))));
int head = 0; int head = 0;
while (head < in.size()) { while (head < in.size())
{
int end = in.indexOf(sep, head); int end = in.indexOf(sep, head);
if (end < 0) if (end < 0)
end = in.size(); end = in.size();

View file

@ -53,7 +53,8 @@ namespace
{ {
QProcess proc; QProcess proc;
proc.start(exeName, {"--version"}, QIODevice::ReadOnly); proc.start(exeName, {"--version"}, QIODevice::ReadOnly);
if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit)) { if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit))
{
QByteArray procOutput = proc.readAllStandardOutput(); QByteArray procOutput = proc.readAllStandardOutput();
if (procOutput.isEmpty()) if (procOutput.isEmpty())
procOutput = proc.readAllStandardError(); procOutput = proc.readAllStandardError();
@ -71,10 +72,12 @@ namespace
const QString versionStr = outputSplit[1]; const QString versionStr = outputSplit[1];
const int idx = versionStr.indexOf(QRegularExpression("[^\\.\\d]")); const int idx = versionStr.indexOf(QRegularExpression("[^\\.\\d]"));
try { try
{
info = {exeName, versionStr.left(idx)}; info = {exeName, versionStr.left(idx)};
} }
catch (const std::runtime_error &) { catch (const std::runtime_error &)
{
return false; return false;
} }
@ -102,12 +105,14 @@ namespace
DWORD cMaxSubKeyLen = 0; DWORD cMaxSubKeyLen = 0;
LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL); LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS)
{
++cMaxSubKeyLen; // For null character ++cMaxSubKeyLen; // For null character
LPWSTR lpName = new WCHAR[cMaxSubKeyLen]; LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
DWORD cName; DWORD cName;
for (DWORD i = 0; i < cSubKeys; ++i) { for (DWORD i = 0; i < cSubKeys; ++i)
{
cName = cMaxSubKeyLen; cName = cMaxSubKeyLen;
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL); res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS) if (res == ERROR_SUCCESS)
@ -127,7 +132,8 @@ namespace
DWORD type = 0; DWORD type = 0;
DWORD cbData = 0; DWORD cbData = 0;
LPWSTR lpValueName = NULL; LPWSTR lpValueName = NULL;
if (!name.isEmpty()) { if (!name.isEmpty())
{
lpValueName = new WCHAR[name.size() + 1]; lpValueName = new WCHAR[name.size() + 1];
name.toWCharArray(lpValueName); name.toWCharArray(lpValueName);
lpValueName[name.size()] = 0; lpValueName[name.size()] = 0;
@ -141,7 +147,8 @@ namespace
if (lpValueName) if (lpValueName)
delete[] lpValueName; delete[] lpValueName;
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS)
{
lpData[cBuffer - 1] = 0; lpData[cBuffer - 1] = 0;
result = QString::fromWCharArray(lpData); result = QString::fromWCharArray(lpData);
} }
@ -169,13 +176,15 @@ namespace
HKEY hkPythonCore; HKEY hkPythonCore;
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore); res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS)
{
QStringList versions = getRegSubkeys(hkPythonCore); QStringList versions = getRegSubkeys(hkPythonCore);
qDebug("Python versions nb: %d", versions.size()); qDebug("Python versions nb: %d", versions.size());
versions.sort(); versions.sort();
bool found = false; bool found = false;
while (!found && !versions.empty()) { while (!found && !versions.empty())
{
const QString version = versions.takeLast() + "\\InstallPath"; const QString version = versions.takeLast() + "\\InstallPath";
LPWSTR lpSubkey = new WCHAR[version.size() + 1]; LPWSTR lpSubkey = new WCHAR[version.size() + 1];
version.toWCharArray(lpSubkey); version.toWCharArray(lpSubkey);
@ -185,19 +194,23 @@ namespace
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath); res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
delete[] lpSubkey; delete[] lpSubkey;
if (res == ERROR_SUCCESS) { if (res == ERROR_SUCCESS)
{
qDebug("Detected possible Python v%s location", qUtf8Printable(version)); qDebug("Detected possible Python v%s location", qUtf8Printable(version));
path = getRegValue(hkInstallPath); path = getRegValue(hkInstallPath);
::RegCloseKey(hkInstallPath); ::RegCloseKey(hkInstallPath);
if (!path.isEmpty()) { if (!path.isEmpty())
{
const QDir baseDir {path}; const QDir baseDir {path};
if (baseDir.exists("python3.exe")) { if (baseDir.exists("python3.exe"))
{
found = true; found = true;
path = baseDir.filePath("python3.exe"); path = baseDir.filePath("python3.exe");
} }
else if (baseDir.exists("python.exe")) { else if (baseDir.exists("python.exe"))
{
found = true; found = true;
path = baseDir.filePath("python.exe"); path = baseDir.filePath("python.exe");
} }
@ -230,7 +243,8 @@ namespace
// Fallback: Detect python from default locations // Fallback: Detect python from default locations
const QFileInfoList dirs = QDir("C:/").entryInfoList({"Python*"}, QDir::Dirs, (QDir::Name | QDir::Reversed)); const QFileInfoList dirs = QDir("C:/").entryInfoList({"Python*"}, QDir::Dirs, (QDir::Name | QDir::Reversed));
for (const QFileInfo &info : dirs) { for (const QFileInfo &info : dirs)
{
const QString py3Path {info.absolutePath() + "/python3.exe"}; const QString py3Path {info.absolutePath() + "/python3.exe"};
if (QFile::exists(py3Path)) if (QFile::exists(py3Path))
return py3Path; return py3Path;
@ -258,7 +272,8 @@ bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
PythonInfo Utils::ForeignApps::pythonInfo() PythonInfo Utils::ForeignApps::pythonInfo()
{ {
static PythonInfo pyInfo; static PythonInfo pyInfo;
if (!pyInfo.isValid()) { if (!pyInfo.isValid())
{
if (testPythonInstallation("python3", pyInfo)) if (testPythonInstallation("python3", pyInfo))
return pyInfo; return pyInfo;

View file

@ -113,7 +113,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
if (path.isEmpty() || !QDir(path).exists()) if (path.isEmpty() || !QDir(path).exists())
return true; return true;
const QStringList deleteFilesList = { const QStringList deleteFilesList =
{
// Windows // Windows
QLatin1String("Thumbs.db"), QLatin1String("Thumbs.db"),
QLatin1String("desktop.ini"), QLatin1String("desktop.ini"),
@ -132,7 +133,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
std::sort(dirList.begin(), dirList.end() std::sort(dirList.begin(), dirList.end()
, [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); }); , [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); });
for (const QString &p : asConst(dirList)) { for (const QString &p : asConst(dirList))
{
const QDir dir(p); const QDir dir(p);
// A deeper folder may have not been removed in the previous iteration // A deeper folder may have not been removed in the previous iteration
// so don't remove anything from this folder either. // so don't remove anything from this folder either.
@ -201,7 +203,8 @@ qint64 Utils::Fs::computePathSize(const QString &path)
// Compute folder size based on its content // Compute folder size based on its content
qint64 size = 0; qint64 size = 0;
QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories); QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories);
while (iter.hasNext()) { while (iter.hasNext())
{
iter.next(); iter.next();
size += iter.fileInfo().size(); size += iter.fileInfo().size();
} }
@ -220,7 +223,8 @@ bool Utils::Fs::sameFiles(const QString &path1, const QString &path2)
if (!f2.open(QIODevice::ReadOnly)) return false; if (!f2.open(QIODevice::ReadOnly)) return false;
const int readSize = 1024 * 1024; // 1 MiB const int readSize = 1024 * 1024; // 1 MiB
while (!f1.atEnd() && !f2.atEnd()) { while (!f1.atEnd() && !f2.atEnd())
{
if (f1.read(readSize) != f2.read(readSize)) if (f1.read(readSize) != f2.read(readSize))
return false; return false;
} }
@ -243,15 +247,18 @@ bool Utils::Fs::isValidFileSystemName(const QString &name, const bool allowSepar
if (name.isEmpty()) return false; if (name.isEmpty()) return false;
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
const QRegularExpression regex {allowSeparators const QRegularExpression regex
{allowSeparators
? QLatin1String("[:?\"*<>|]") ? QLatin1String("[:?\"*<>|]")
: QLatin1String("[\\\\/:?\"*<>|]")}; : QLatin1String("[\\\\/:?\"*<>|]")};
#elif defined(Q_OS_MACOS) #elif defined(Q_OS_MACOS)
const QRegularExpression regex {allowSeparators const QRegularExpression regex
{allowSeparators
? QLatin1String("[\\0:]") ? QLatin1String("[\\0:]")
: QLatin1String("[\\0/:]")}; : QLatin1String("[\\0/:]")};
#else #else
const QRegularExpression regex {allowSeparators const QRegularExpression regex
{allowSeparators
? QLatin1String("[\\0]") ? QLatin1String("[\\0]")
: QLatin1String("[\\0/]")}; : QLatin1String("[\\0/]")};
#endif #endif
@ -271,7 +278,8 @@ QString Utils::Fs::branchPath(const QString &filePath, QString *removed)
if (ret.endsWith('/')) if (ret.endsWith('/'))
ret.chop(1); ret.chop(1);
const int slashIndex = ret.lastIndexOf('/'); const int slashIndex = ret.lastIndexOf('/');
if (slashIndex >= 0) { if (slashIndex >= 0)
{
if (removed) if (removed)
*removed = ret.mid(slashIndex + 1); *removed = ret.mid(slashIndex + 1);
ret = ret.left(slashIndex); ret = ret.left(slashIndex);
@ -312,7 +320,8 @@ QString Utils::Fs::tempPath()
bool Utils::Fs::isRegularFile(const QString &path) bool Utils::Fs::isRegularFile(const QString &path)
{ {
struct ::stat st; struct ::stat st;
if (::stat(path.toUtf8().constData(), &st) != 0) { if (::stat(path.toUtf8().constData(), &st) != 0)
{
// analyse erno and log the error // analyse erno and log the error
const auto err = errno; const auto err = errno;
qDebug("Could not get file stats for path '%s'. Error: %s" qDebug("Could not get file stats for path '%s'. Error: %s"
@ -360,7 +369,8 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
// Magic number references: // Magic number references:
// 1. /usr/include/linux/magic.h // 1. /usr/include/linux/magic.h
// 2. https://github.com/coreutils/coreutils/blob/master/src/stat.c // 2. https://github.com/coreutils/coreutils/blob/master/src/stat.c
switch (static_cast<unsigned int>(buf.f_type)) { switch (static_cast<unsigned int>(buf.f_type))
{
case 0xFF534D42: // CIFS_MAGIC_NUMBER case 0xFF534D42: // CIFS_MAGIC_NUMBER
case 0x6969: // NFS_SUPER_MAGIC case 0x6969: // NFS_SUPER_MAGIC
case 0x517B: // SMB_SUPER_MAGIC case 0x517B: // SMB_SUPER_MAGIC

View file

@ -68,10 +68,12 @@ QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *
output.reserve(deflateBound(&strm, data.size())); output.reserve(deflateBound(&strm, data.size()));
// feed to deflate // feed to deflate
while (strm.avail_in > 0) { while (strm.avail_in > 0)
{
result = deflate(&strm, Z_NO_FLUSH); result = deflate(&strm, Z_NO_FLUSH);
if (result != Z_OK) { if (result != Z_OK)
{
deflateEnd(&strm); deflateEnd(&strm);
return {}; return {};
} }
@ -82,7 +84,8 @@ QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *
} }
// flush the rest from deflate // flush the rest from deflate
while (result != Z_STREAM_END) { while (result != Z_STREAM_END)
{
result = deflate(&strm, Z_FINISH); result = deflate(&strm, Z_FINISH);
output.append(tmpBuf.data(), (BUFSIZE - strm.avail_out)); output.append(tmpBuf.data(), (BUFSIZE - strm.avail_out));
@ -126,15 +129,18 @@ QByteArray Utils::Gzip::decompress(const QByteArray &data, bool *ok)
output.reserve(data.size() * 3); output.reserve(data.size() * 3);
// run inflate // run inflate
while (true) { while (true)
{
result = inflate(&strm, Z_NO_FLUSH); result = inflate(&strm, Z_NO_FLUSH);
if (result == Z_STREAM_END) { if (result == Z_STREAM_END)
{
output.append(tmpBuf.data(), (BUFSIZE - strm.avail_out)); output.append(tmpBuf.data(), (BUFSIZE - strm.avail_out));
break; break;
} }
if (result != Z_OK) { if (result != Z_OK)
{
inflateEnd(&strm); inflateEnd(&strm);
return {}; return {};
} }

View file

@ -41,7 +41,8 @@ Utils::IO::FileDeviceOutputIterator::FileDeviceOutputIterator(QFileDevice &devic
Utils::IO::FileDeviceOutputIterator::~FileDeviceOutputIterator() Utils::IO::FileDeviceOutputIterator::~FileDeviceOutputIterator()
{ {
if (m_buffer.use_count() == 1) { if (m_buffer.use_count() == 1)
{
if (m_device->error() == QFileDevice::NoError) if (m_device->error() == QFileDevice::NoError)
m_device->write(*m_buffer); m_device->write(*m_buffer);
m_buffer->clear(); m_buffer->clear();
@ -51,7 +52,8 @@ Utils::IO::FileDeviceOutputIterator::~FileDeviceOutputIterator()
Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operator=(const char c) Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operator=(const char c)
{ {
m_buffer->append(c); m_buffer->append(c);
if (m_buffer->size() >= m_bufferSize) { if (m_buffer->size() >= m_bufferSize)
{
if (m_device->error() == QFileDevice::NoError) if (m_device->error() == QFileDevice::NoError)
m_device->write(*m_buffer); m_device->write(*m_buffer);
m_buffer->clear(); m_buffer->clear();

View file

@ -63,7 +63,8 @@
namespace namespace
{ {
const struct { const char *source; const char *comment; } units[] = { const struct { const char *source; const char *comment; } units[] =
{
QT_TRANSLATE_NOOP3("misc", "B", "bytes"), QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"), QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"), QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
@ -85,7 +86,8 @@ namespace
int i = 0; int i = 0;
val = static_cast<qreal>(sizeInBytes); val = static_cast<qreal>(sizeInBytes);
while ((val >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte))) { while ((val >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
{
val /= 1024.; val /= 1024.;
++i; ++i;
} }
@ -119,13 +121,16 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
if (GetLastError() != ERROR_SUCCESS) if (GetLastError() != ERROR_SUCCESS)
return; return;
if (action == ShutdownDialogAction::Suspend) { if (action == ShutdownDialogAction::Suspend)
{
::SetSuspendState(false, false, false); ::SetSuspendState(false, false, false);
} }
else if (action == ShutdownDialogAction::Hibernate) { else if (action == ShutdownDialogAction::Hibernate)
{
::SetSuspendState(true, false, false); ::SetSuspendState(true, false, false);
} }
else { else
{
const QString msg = QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete."); const QString msg = QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.");
auto msgWchar = std::make_unique<wchar_t[]>(msg.length() + 1); auto msgWchar = std::make_unique<wchar_t[]>(msg.length() + 1);
msg.toWCharArray(msgWchar.get()); msg.toWCharArray(msgWchar.get());
@ -171,11 +176,13 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
#elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB)) #elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB))
// Use dbus to power off / suspend the system // Use dbus to power off / suspend the system
if (action != ShutdownDialogAction::Shutdown) { if (action != ShutdownDialogAction::Shutdown)
{
// Some recent systems use systemd's logind // Some recent systems use systemd's logind
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1", QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
"org.freedesktop.login1.Manager", QDBusConnection::systemBus()); "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
if (login1Iface.isValid()) { if (login1Iface.isValid())
{
if (action == ShutdownDialogAction::Suspend) if (action == ShutdownDialogAction::Suspend)
login1Iface.call("Suspend", false); login1Iface.call("Suspend", false);
else else
@ -185,7 +192,8 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
// Else, other recent systems use UPower // Else, other recent systems use UPower
QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower", QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower",
"org.freedesktop.UPower", QDBusConnection::systemBus()); "org.freedesktop.UPower", QDBusConnection::systemBus());
if (upowerIface.isValid()) { if (upowerIface.isValid())
{
if (action == ShutdownDialogAction::Suspend) if (action == ShutdownDialogAction::Suspend)
upowerIface.call("Suspend"); upowerIface.call("Suspend");
else else
@ -201,18 +209,21 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
else else
halIface.call("Hibernate"); halIface.call("Hibernate");
} }
else { else
{
// Some recent systems use systemd's logind // Some recent systems use systemd's logind
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1", QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
"org.freedesktop.login1.Manager", QDBusConnection::systemBus()); "org.freedesktop.login1.Manager", QDBusConnection::systemBus());
if (login1Iface.isValid()) { if (login1Iface.isValid())
{
login1Iface.call("PowerOff", false); login1Iface.call("PowerOff", false);
return; return;
} }
// Else, other recent systems use ConsoleKit // Else, other recent systems use ConsoleKit
QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager",
"org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus()); "org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
if (consolekitIface.isValid()) { if (consolekitIface.isValid())
{
consolekitIface.call("Stop"); consolekitIface.call("Stop");
return; return;
} }
@ -251,7 +262,8 @@ QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed)
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit) int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
{ {
// friendlyUnit's number of digits after the decimal point // friendlyUnit's number of digits after the decimal point
switch (unit) { switch (unit)
{
case SizeUnit::Byte: case SizeUnit::Byte:
return 0; return 0;
case SizeUnit::KibiByte: case SizeUnit::KibiByte:
@ -273,7 +285,8 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
bool Utils::Misc::isPreviewable(const QString &extension) bool Utils::Misc::isPreviewable(const QString &extension)
{ {
static const QSet<QString> multimediaExtensions = { static const QSet<QString> multimediaExtensions =
{
"3GP", "3GP",
"AAC", "AAC",
"AC3", "AC3",
@ -338,13 +351,15 @@ QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglo
return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes)); return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes));
qlonglong hours = (minutes / 60); qlonglong hours = (minutes / 60);
if (hours < 24) { if (hours < 24)
{
minutes -= (hours * 60); minutes -= (hours * 60);
return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes)); return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes));
} }
qlonglong days = (hours / 24); qlonglong days = (hours / 24);
if (days < 365) { if (days < 365)
{
hours -= (days * 24); hours -= (days * 24);
return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days), QString::number(hours)); return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days), QString::number(hours));
} }
@ -479,7 +494,8 @@ QString Utils::Misc::zlibVersionString()
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString Utils::Misc::windowsSystemPath() QString Utils::Misc::windowsSystemPath()
{ {
static const QString path = []() -> QString { static const QString path = []() -> QString
{
WCHAR systemPath[MAX_PATH] = {0}; WCHAR systemPath[MAX_PATH] = {0};
GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR)); GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
return QString::fromWCharArray(systemPath); return QString::fromWCharArray(systemPath);

View file

@ -70,12 +70,14 @@ namespace Utils
QHostAddress protocolEquivalentAddress; QHostAddress protocolEquivalentAddress;
bool addrConversionOk = false; bool addrConversionOk = false;
if (addr.protocol() == QAbstractSocket::IPv4Protocol) { if (addr.protocol() == QAbstractSocket::IPv4Protocol)
{
// always succeeds // always succeeds
protocolEquivalentAddress = QHostAddress(addr.toIPv6Address()); protocolEquivalentAddress = QHostAddress(addr.toIPv6Address());
addrConversionOk = true; addrConversionOk = true;
} }
else { else
{
// only succeeds when addr is an ipv4-mapped ipv6 address // only succeeds when addr is an ipv4-mapped ipv6 address
protocolEquivalentAddress = QHostAddress(addr.toIPv4Address(&addrConversionOk)); protocolEquivalentAddress = QHostAddress(addr.toIPv4Address(&addrConversionOk));
} }

View file

@ -71,7 +71,8 @@ QByteArray Utils::Password::PBKDF2::generate(const QString &password)
QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password) QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password)
{ {
const std::array<uint32_t, 4> salt {{Random::rand(), Random::rand() const std::array<uint32_t, 4> salt
{{Random::rand(), Random::rand()
, Random::rand(), Random::rand()}}; , Random::rand(), Random::rand()}};
std::array<unsigned char, 64> outBuf {}; std::array<unsigned char, 64> outBuf {};

View file

@ -82,7 +82,8 @@ namespace
int posL = 0; int posL = 0;
int posR = 0; int posR = 0;
while (true) { while (true)
{
if ((posL == left.size()) || (posR == right.size())) if ((posL == left.size()) || (posR == right.size()))
return (left.size() - right.size()); // when a shorter string is another string's prefix, shorter string place before longer string return (left.size() - right.size()); // when a shorter string is another string's prefix, shorter string place before longer string
@ -91,12 +92,14 @@ namespace
// Compare only non-digits. // Compare only non-digits.
// Numbers should be compared as a whole // Numbers should be compared as a whole
// otherwise the string->int conversion can yield a wrong value // otherwise the string->int conversion can yield a wrong value
if ((leftChar == rightChar) && !leftChar.isDigit()) { if ((leftChar == rightChar) && !leftChar.isDigit())
{
// compare next character // compare next character
++posL; ++posL;
++posR; ++posR;
} }
else if (leftChar.isDigit() && rightChar.isDigit()) { else if (leftChar.isDigit() && rightChar.isDigit())
{
// Both are digits, compare the numbers // Both are digits, compare the numbers
const auto numberView = [](const QString &str, int &pos) -> QStringRef const auto numberView = [](const QString &str, int &pos) -> QStringRef
@ -114,7 +117,8 @@ namespace
return (numViewL.length() - numViewR.length()); return (numViewL.length() - numViewR.length());
// both string/view has the same length // both string/view has the same length
for (int i = 0; i < numViewL.length(); ++i) { for (int i = 0; i < numViewL.length(); ++i)
{
const QChar numL = numViewL[i]; const QChar numL = numViewL[i];
const QChar numR = numViewR[i]; const QChar numR = numViewR[i];
@ -125,7 +129,8 @@ namespace
// String + digits do match and we haven't hit the end of both strings // String + digits do match and we haven't hit the end of both strings
// then continue to consume the remainings // then continue to consume the remainings
} }
else { else
{
return (leftChar.unicode() - rightChar.unicode()); return (leftChar.unicode() - rightChar.unicode());
} }
} }
@ -140,7 +145,8 @@ int Utils::String::naturalCompare(const QString &left, const QString &right, con
{ {
// provide a single `NaturalCompare` instance for easy use // provide a single `NaturalCompare` instance for easy use
// https://doc.qt.io/qt-5/threads-reentrancy.html // https://doc.qt.io/qt-5/threads-reentrancy.html
if (caseSensitivity == Qt::CaseSensitive) { if (caseSensitivity == Qt::CaseSensitive)
{
#ifdef QBT_USES_QTHREADSTORAGE #ifdef QBT_USES_QTHREADSTORAGE
static QThreadStorage<NaturalCompare> nCmp; static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData()) if (!nCmp.hasLocalData())

View file

@ -58,7 +58,8 @@ namespace Utils
{ {
if (str.length() < 2) return str; if (str.length() < 2) return str;
for (const QChar quote : quotes) { for (const QChar quote : quotes)
{
if (str.startsWith(quote) && str.endsWith(quote)) if (str.startsWith(quote) && str.endsWith(quote))
return str.mid(1, (str.length() - 2)); return str.mid(1, (str.length() - 2));
} }

View file

@ -151,10 +151,12 @@ namespace Utils
template <typename StringClassWithSplitMethod> template <typename StringClassWithSplitMethod>
static Version tryParse(const StringClassWithSplitMethod &s, const Version &defaultVersion) static Version tryParse(const StringClassWithSplitMethod &s, const Version &defaultVersion)
{ {
try { try
{
return Version(s); return Version(s);
} }
catch (const std::runtime_error &er) { catch (const std::runtime_error &er)
{
qDebug() << "Error parsing version:" << er.what(); qDebug() << "Error parsing version:" << er.what();
return defaultVersion; return defaultVersion;
} }
@ -172,7 +174,8 @@ namespace Utils
bool ok = false; bool ok = false;
ComponentsArray res {{}}; ComponentsArray res {{}};
for (std::size_t i = 0; i < static_cast<std::size_t>(versionParts.size()); ++i) { for (std::size_t i = 0; i < static_cast<std::size_t>(versionParts.size()); ++i)
{
res[i] = static_cast<T>(versionParts[static_cast<typename StringsList::size_type>(i)].toInt(&ok)); res[i] = static_cast<T>(versionParts[static_cast<typename StringsList::size_type>(i)].toInt(&ok));
if (!ok) if (!ok)
throw std::runtime_error("Can not parse version component"); throw std::runtime_error("Can not parse version component");

View file

@ -70,21 +70,24 @@ AboutDialog::AboutDialog(QWidget *parent)
// Thanks // Thanks
QFile thanksfile(":/thanks.html"); QFile thanksfile(":/thanks.html");
if (thanksfile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (thanksfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
m_ui->textBrowserThanks->setHtml(QString::fromUtf8(thanksfile.readAll().constData())); m_ui->textBrowserThanks->setHtml(QString::fromUtf8(thanksfile.readAll().constData()));
thanksfile.close(); thanksfile.close();
} }
// Translation // Translation
QFile translatorsfile(":/translators.html"); QFile translatorsfile(":/translators.html");
if (translatorsfile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (translatorsfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
m_ui->textBrowserTranslation->setHtml(QString::fromUtf8(translatorsfile.readAll().constData())); m_ui->textBrowserTranslation->setHtml(QString::fromUtf8(translatorsfile.readAll().constData()));
translatorsfile.close(); translatorsfile.close();
} }
// License // License
QFile licensefile(":/gpl.html"); QFile licensefile(":/gpl.html");
if (licensefile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (licensefile.open(QIODevice::ReadOnly | QIODevice::Text))
{
m_ui->textBrowserLicense->setHtml(QString::fromUtf8(licensefile.readAll().constData())); m_ui->textBrowserLicense->setHtml(QString::fromUtf8(licensefile.readAll().constData()));
licensefile.close(); licensefile.close();
} }

View file

@ -230,7 +230,8 @@ void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorre
{ {
auto *dlg = new AddNewTorrentDialog(inParams, parent); auto *dlg = new AddNewTorrentDialog(inParams, parent);
if (Net::DownloadManager::hasSupportedScheme(source)) { if (Net::DownloadManager::hasSupportedScheme(source))
{
// Launch downloader // Launch downloader
Net::DownloadManager::instance()->download( Net::DownloadManager::instance()->download(
Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE) Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
@ -262,7 +263,8 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
QString error; QString error;
m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(decodedPath, &error); m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(decodedPath, &error);
if (!m_torrentInfo.isValid()) { if (!m_torrentInfo.isValid())
{
RaisedMessageBox::critical(this, tr("Invalid torrent") RaisedMessageBox::critical(this, tr("Invalid torrent")
, tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.") , tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.")
.arg(Utils::Fs::toNativePath(decodedPath), error)); .arg(Utils::Fs::toNativePath(decodedPath), error));
@ -280,19 +282,24 @@ bool AddNewTorrentDialog::loadTorrentImpl()
const BitTorrent::InfoHash infoHash = m_torrentInfo.hash(); const BitTorrent::InfoHash infoHash = m_torrentInfo.hash();
// Prevent showing the dialog if download is already present // Prevent showing the dialog if download is already present
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash)) { if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
{
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash); BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
if (torrent) { if (torrent)
if (torrent->isPrivate() || m_torrentInfo.isPrivate()) { {
if (torrent->isPrivate() || m_torrentInfo.isPrivate())
{
RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok); RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok);
} }
else { else
{
torrent->addTrackers(m_torrentInfo.trackers()); torrent->addTrackers(m_torrentInfo.trackers());
torrent->addUrlSeeds(m_torrentInfo.urlSeeds()); torrent->addUrlSeeds(m_torrentInfo.urlSeeds());
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok); RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok);
} }
} }
else { else
{
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok); RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok);
} }
return false; return false;
@ -307,7 +314,8 @@ bool AddNewTorrentDialog::loadTorrentImpl()
bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri) bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
{ {
if (!magnetUri.isValid()) { if (!magnetUri.isValid())
{
RaisedMessageBox::critical(this, tr("Invalid magnet link"), tr("This magnet link was not recognized")); RaisedMessageBox::critical(this, tr("Invalid magnet link"), tr("This magnet link was not recognized"));
return false; return false;
} }
@ -316,19 +324,24 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
const BitTorrent::InfoHash infoHash = magnetUri.hash(); const BitTorrent::InfoHash infoHash = magnetUri.hash();
// Prevent showing the dialog if download is already present // Prevent showing the dialog if download is already present
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash)) { if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
{
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash); BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
if (torrent) { if (torrent)
if (torrent->isPrivate()) { {
if (torrent->isPrivate())
{
RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok); RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok);
} }
else { else
{
torrent->addTrackers(magnetUri.trackers()); torrent->addTrackers(magnetUri.trackers());
torrent->addUrlSeeds(magnetUri.urlSeeds()); torrent->addUrlSeeds(magnetUri.urlSeeds());
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok); RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok);
} }
} }
else { else
{
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link is already queued for processing."), QMessageBox::Ok); RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link is already queued for processing."), QMessageBox::Ok);
} }
return false; return false;
@ -395,15 +408,18 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
// Determine torrent size // Determine torrent size
qlonglong torrentSize = 0; qlonglong torrentSize = 0;
if (m_hasMetadata) { if (m_hasMetadata)
if (m_contentModel) { {
if (m_contentModel)
{
const QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities(); const QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount()); Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
for (int i = 0; i < priorities.size(); ++i) for (int i = 0; i < priorities.size(); ++i)
if (priorities[i] > BitTorrent::DownloadPriority::Ignored) if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
torrentSize += m_torrentInfo.fileSize(i); torrentSize += m_torrentInfo.fileSize(i);
} }
else { else
{
torrentSize = m_torrentInfo.totalSize(); torrentSize = m_torrentInfo.totalSize();
} }
} }
@ -426,7 +442,8 @@ void AddNewTorrentDialog::categoryChanged(int index)
{ {
Q_UNUSED(index); Q_UNUSED(index);
if (m_ui->comboTTM->currentIndex() == 1) { if (m_ui->comboTTM->currentIndex() == 1)
{
QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText()); QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText());
m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath)); m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath));
updateDiskSpaceLabel(); updateDiskSpaceLabel();
@ -436,7 +453,8 @@ void AddNewTorrentDialog::categoryChanged(int index)
void AddNewTorrentDialog::setSavePath(const QString &newPath) void AddNewTorrentDialog::setSavePath(const QString &newPath)
{ {
int existingIndex = indexOfSavePath(newPath); int existingIndex = indexOfSavePath(newPath);
if (existingIndex < 0) { if (existingIndex < 0)
{
// New path, prepend to combo box // New path, prepend to combo box
m_ui->savePath->insertItem(0, newPath); m_ui->savePath->insertItem(0, newPath);
existingIndex = 0; existingIndex = 0;
@ -461,10 +479,12 @@ void AddNewTorrentDialog::saveTorrentFile()
if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive)) if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive))
path += torrentFileExtension; path += torrentFileExtension;
try { try
{
m_torrentInfo.saveToFile(path); m_torrentInfo.saveToFile(path);
} }
catch (const RuntimeError &err) { catch (const RuntimeError &err)
{
QMessageBox::critical(this, tr("I/O Error"), err.message()); QMessageBox::critical(this, tr("I/O Error"), err.message());
} }
} }
@ -494,7 +514,8 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio) const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
{ {
for (const QModelIndex &index : selectedRows) { for (const QModelIndex &index : selectedRows)
{
m_contentModel->setData( m_contentModel->setData(
m_contentModel->index(index.row(), PRIORITY, index.parent()) m_contentModel->index(index.row(), PRIORITY, index.parent())
, static_cast<int>(prio)); , static_cast<int>(prio));
@ -504,7 +525,8 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1) { if (selectedRows.size() == 1)
{
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")); QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); }); connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
@ -563,12 +585,14 @@ void AddNewTorrentDialog::accept()
m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked(); m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
QString savePath = m_ui->savePath->selectedPath(); QString savePath = m_ui->savePath->selectedPath();
if (m_ui->comboTTM->currentIndex() != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode. if (m_ui->comboTTM->currentIndex() != 1)
{ // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
m_torrentParams.useAutoTMM = TriStateBool::False; m_torrentParams.useAutoTMM = TriStateBool::False;
m_torrentParams.savePath = savePath; m_torrentParams.savePath = savePath;
saveSavePathHistory(); saveSavePathHistory();
} }
else { else
{
m_torrentParams.useAutoTMM = TriStateBool::True; m_torrentParams.useAutoTMM = TriStateBool::True;
} }
@ -586,7 +610,8 @@ void AddNewTorrentDialog::accept()
void AddNewTorrentDialog::reject() void AddNewTorrentDialog::reject()
{ {
if (!m_hasMetadata) { if (!m_hasMetadata)
{
setMetadataProgressIndicator(false); setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelLoadMetadata(m_magnetURI.hash()); BitTorrent::Session::instance()->cancelLoadMetadata(m_magnetURI.hash());
} }
@ -600,7 +625,8 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata); disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata);
if (!metadata.isValid()) { if (!metadata.isValid())
{
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata.")); RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
setMetadataProgressIndicator(false, tr("Invalid metadata")); setMetadataProgressIndicator(false, tr("Invalid metadata"));
return; return;
@ -628,11 +654,13 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
void AddNewTorrentDialog::setupTreeview() void AddNewTorrentDialog::setupTreeview()
{ {
if (!m_hasMetadata) { if (!m_hasMetadata)
{
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable")); m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable")); m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
} }
else { else
{
// Set dialog title // Set dialog title
setWindowTitle(m_torrentInfo.name()); setWindowTitle(m_torrentInfo.name());
@ -662,7 +690,8 @@ void AddNewTorrentDialog::setupTreeview()
// Expand single-item folders recursively // Expand single-item folders recursively
QModelIndex currentIndex; QModelIndex currentIndex;
while (m_contentModel->rowCount(currentIndex) == 1) { while (m_contentModel->rowCount(currentIndex) == 1)
{
currentIndex = m_contentModel->index(0, 0, currentIndex); currentIndex = m_contentModel->index(0, 0, currentIndex);
m_ui->contentTreeView->setExpanded(currentIndex, true); m_ui->contentTreeView->setExpanded(currentIndex, true);
} }
@ -674,10 +703,12 @@ void AddNewTorrentDialog::setupTreeview()
void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &result) void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &result)
{ {
QString error; QString error;
switch (result.status) { switch (result.status)
{
case Net::DownloadStatus::Success: case Net::DownloadStatus::Success:
m_torrentInfo = BitTorrent::TorrentInfo::load(result.data, &error); m_torrentInfo = BitTorrent::TorrentInfo::load(result.data, &error);
if (!m_torrentInfo.isValid()) { if (!m_torrentInfo.isValid())
{
RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2") RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
.arg(result.url, error)); .arg(result.url, error));
return; return;
@ -705,13 +736,15 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &resu
void AddNewTorrentDialog::TMMChanged(int index) void AddNewTorrentDialog::TMMChanged(int index)
{ {
if (index != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode. if (index != 1)
{ // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
populateSavePathComboBox(); populateSavePathComboBox();
m_ui->groupBoxSavePath->setEnabled(true); m_ui->groupBoxSavePath->setEnabled(true);
m_ui->savePath->blockSignals(false); m_ui->savePath->blockSignals(false);
m_ui->savePath->setCurrentIndex(m_oldIndex < m_ui->savePath->count() ? m_oldIndex : m_ui->savePath->count() - 1); m_ui->savePath->setCurrentIndex(m_oldIndex < m_ui->savePath->count() ? m_oldIndex : m_ui->savePath->count() - 1);
} }
else { else
{
m_ui->groupBoxSavePath->setEnabled(false); m_ui->groupBoxSavePath->setEnabled(false);
m_ui->savePath->blockSignals(true); m_ui->savePath->blockSignals(true);
m_ui->savePath->clear(); m_ui->savePath->clear();

View file

@ -168,7 +168,8 @@ void AdvancedSettings::saveAdvancedSettings()
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal; BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
switch (m_comboBoxOSMemoryPriority.currentIndex()) { switch (m_comboBoxOSMemoryPriority.currentIndex())
{
case 0: case 0:
default: default:
prio = BitTorrent::OSMemoryPriority::Normal; prio = BitTorrent::OSMemoryPriority::Normal;
@ -248,12 +249,14 @@ void AdvancedSettings::saveAdvancedSettings()
pref->resolvePeerCountries(m_checkBoxResolveCountries.isChecked()); pref->resolvePeerCountries(m_checkBoxResolveCountries.isChecked());
pref->resolvePeerHostNames(m_checkBoxResolveHosts.isChecked()); pref->resolvePeerHostNames(m_checkBoxResolveHosts.isChecked());
// Network interface // Network interface
if (m_comboBoxInterface.currentIndex() == 0) { if (m_comboBoxInterface.currentIndex() == 0)
{
// All interfaces (default) // All interfaces (default)
session->setNetworkInterface(QString()); session->setNetworkInterface(QString());
session->setNetworkInterfaceName(QString()); session->setNetworkInterfaceName(QString());
} }
else { else
{
session->setNetworkInterface(m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString()); session->setNetworkInterface(m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString());
session->setNetworkInterfaceName(m_comboBoxInterface.currentText()); session->setNetworkInterfaceName(m_comboBoxInterface.currentText());
} }
@ -336,21 +339,25 @@ void AdvancedSettings::updateInterfaceAddressCombo()
const auto populateCombo = [this](const QHostAddress &addr) const auto populateCombo = [this](const QHostAddress &addr)
{ {
if (addr.protocol() == QAbstractSocket::IPv4Protocol) { if (addr.protocol() == QAbstractSocket::IPv4Protocol)
{
const QString str = addr.toString(); const QString str = addr.toString();
m_comboBoxInterfaceAddress.addItem(str, str); m_comboBoxInterfaceAddress.addItem(str, str);
} }
else if (addr.protocol() == QAbstractSocket::IPv6Protocol) { else if (addr.protocol() == QAbstractSocket::IPv6Protocol)
{
const QString str = Utils::Net::canonicalIPv6Addr(addr).toString(); const QString str = Utils::Net::canonicalIPv6Addr(addr).toString();
m_comboBoxInterfaceAddress.addItem(str, str); m_comboBoxInterfaceAddress.addItem(str, str);
} }
}; };
if (ifaceName.isEmpty()) { if (ifaceName.isEmpty())
{
for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses())) for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
populateCombo(addr); populateCombo(addr);
} }
else { else
{
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName); const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
const QList<QNetworkAddressEntry> addresses = iface.addressEntries(); const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
for (const QNetworkAddressEntry &entry : addresses) for (const QNetworkAddressEntry &entry : addresses)
@ -386,7 +393,8 @@ void AdvancedSettings::loadAdvancedSettings()
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")}); m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
int OSMemoryPriorityIndex = 0; int OSMemoryPriorityIndex = 0;
switch (session->getOSMemoryPriority()) { switch (session->getOSMemoryPriority())
{
default: default:
case BitTorrent::OSMemoryPriority::Normal: case BitTorrent::OSMemoryPriority::Normal:
OSMemoryPriorityIndex = 0; OSMemoryPriorityIndex = 0;
@ -580,16 +588,19 @@ void AdvancedSettings::loadAdvancedSettings()
const QString currentInterface = session->networkInterface(); const QString currentInterface = session->networkInterface();
bool interfaceExists = currentInterface.isEmpty(); bool interfaceExists = currentInterface.isEmpty();
int i = 1; int i = 1;
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces())) { for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces()))
{
m_comboBoxInterface.addItem(iface.humanReadableName(), iface.name()); m_comboBoxInterface.addItem(iface.humanReadableName(), iface.name());
if (!currentInterface.isEmpty() && (iface.name() == currentInterface)) { if (!currentInterface.isEmpty() && (iface.name() == currentInterface))
{
m_comboBoxInterface.setCurrentIndex(i); m_comboBoxInterface.setCurrentIndex(i);
interfaceExists = true; interfaceExists = true;
} }
++i; ++i;
} }
// Saved interface does not exist, show it anyway // Saved interface does not exist, show it anyway
if (!interfaceExists) { if (!interfaceExists)
{
m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface); m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface);
m_comboBoxInterface.setCurrentIndex(i); m_comboBoxInterface.setCurrentIndex(i);
} }

View file

@ -55,7 +55,8 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con
d.m_ui->textEdit->setInputMethodHints(inputMethodHints); d.m_ui->textEdit->setInputMethodHints(inputMethodHints);
d.m_ui->textEdit->selectAll(); d.m_ui->textEdit->selectAll();
if (excludeExtension) { if (excludeExtension)
{
int lastDotIndex = text.lastIndexOf('.'); int lastDotIndex = text.lastIndexOf('.');
if ((lastDotIndex > 3) && (text.mid(lastDotIndex - 4, 4).toLower() == ".tar")) if ((lastDotIndex > 3) && (text.mid(lastDotIndex - 4, 4).toLower() == ".tar"))
lastDotIndex -= 4; lastDotIndex -= 4;
@ -86,7 +87,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4; int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
#endif #endif
if (!windowTitle().isEmpty()) { if (!windowTitle().isEmpty())
{
// not really the font metrics in window title, so we enlarge it a bit, // not really the font metrics in window title, so we enlarge it a bit,
// including the small icon and close button width // including the small icon and close button width
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
@ -97,7 +99,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
wd = std::max(wd, w); wd = std::max(wd, w);
} }
if (!m_ui->textLabel->text().isEmpty()) { if (!m_ui->textLabel->text().isEmpty())
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text()); int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
#else #else
@ -109,7 +112,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
// Now resize the dialog to fit the contents // Now resize the dialog to fit the contents
// max width of text from either of: label, title, textedit // max width of text from either of: label, title, textedit
// If the value is less than dialog default size, default size is used // If the value is less than dialog default size, default size is used
if (wd > width()) { if (wd > width())
{
QSize size = {width() - m_ui->verticalLayout->sizeHint().width() + wd, height()}; QSize size = {width() - m_ui->verticalLayout->sizeHint().width() + wd, height()};
Utils::Gui::resize(this, size); Utils::Gui::resize(this, size);
} }

View file

@ -64,18 +64,21 @@ BanListOptionsDialog::~BanListOptionsDialog()
void BanListOptionsDialog::on_buttonBox_accepted() void BanListOptionsDialog::on_buttonBox_accepted()
{ {
if (m_modified) { if (m_modified)
{
// save to session // save to session
QStringList IPList; QStringList IPList;
// Operate on the m_sortFilter to grab the strings in sorted order // Operate on the m_sortFilter to grab the strings in sorted order
for (int i = 0; i < m_sortFilter->rowCount(); ++i) { for (int i = 0; i < m_sortFilter->rowCount(); ++i)
{
QModelIndex index = m_sortFilter->index(i, 0); QModelIndex index = m_sortFilter->index(i, 0);
IPList << index.data().toString(); IPList << index.data().toString();
} }
BitTorrent::Session::instance()->setBannedIPs(IPList); BitTorrent::Session::instance()->setBannedIPs(IPList);
QDialog::accept(); QDialog::accept();
} }
else { else
{
QDialog::reject(); QDialog::reject();
} }
} }
@ -83,7 +86,8 @@ void BanListOptionsDialog::on_buttonBox_accepted()
void BanListOptionsDialog::on_buttonBanIP_clicked() void BanListOptionsDialog::on_buttonBanIP_clicked()
{ {
QString ip = m_ui->txtIP->text(); QString ip = m_ui->txtIP->text();
if (!Utils::Net::isValidIP(ip)) { if (!Utils::Net::isValidIP(ip))
{
QMessageBox::warning(this, tr("Warning"), tr("The entered IP address is invalid.")); QMessageBox::warning(this, tr("Warning"), tr("The entered IP address is invalid."));
return; return;
} }
@ -91,9 +95,11 @@ void BanListOptionsDialog::on_buttonBanIP_clicked()
// QHostAddress::toString() result format follows RFC5952; // QHostAddress::toString() result format follows RFC5952;
// thus we avoid duplicate entries pointing to the same address // thus we avoid duplicate entries pointing to the same address
ip = QHostAddress(ip).toString(); ip = QHostAddress(ip).toString();
for (int i = 0; i < m_sortFilter->rowCount(); ++i) { for (int i = 0; i < m_sortFilter->rowCount(); ++i)
{
QModelIndex index = m_sortFilter->index(i, 0); QModelIndex index = m_sortFilter->index(i, 0);
if (ip == index.data().toString()) { if (ip == index.data().toString())
{
QMessageBox::warning(this, tr("Warning"), tr("The entered IP is already banned.")); QMessageBox::warning(this, tr("Warning"), tr("The entered IP is already banned."));
return; return;
} }

View file

@ -57,7 +57,8 @@ public:
~CategoryModelItem() ~CategoryModelItem()
{ {
clear(); clear();
if (m_parent) { if (m_parent)
{
m_parent->m_torrentsCount -= m_torrentsCount; m_parent->m_torrentsCount -= m_torrentsCount;
const QString uid = m_parent->m_children.key(this); const QString uid = m_parent->m_children.key(this);
m_parent->m_children.remove(uid); m_parent->m_children.remove(uid);
@ -211,16 +212,19 @@ QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const
auto item = static_cast<const CategoryModelItem *>(index.internalPointer()); auto item = static_cast<const CategoryModelItem *>(index.internalPointer());
if ((index.column() == 0) && (role == Qt::DecorationRole)) { if ((index.column() == 0) && (role == Qt::DecorationRole))
{
return UIThemeManager::instance()->getIcon("inode-directory"); return UIThemeManager::instance()->getIcon("inode-directory");
} }
if ((index.column() == 0) && (role == Qt::DisplayRole)) { if ((index.column() == 0) && (role == Qt::DisplayRole))
{
return QString(QStringLiteral("%1 (%2)")) return QString(QStringLiteral("%1 (%2)"))
.arg(item->name()).arg(item->torrentsCount()); .arg(item->name()).arg(item->torrentsCount());
} }
if ((index.column() == 0) && (role == Qt::UserRole)) { if ((index.column() == 0) && (role == Qt::UserRole))
{
return item->torrentsCount(); return item->torrentsCount();
} }
@ -306,7 +310,8 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName)
{ {
CategoryModelItem *parent = m_rootItem; CategoryModelItem *parent = m_rootItem;
if (m_isSubcategoriesEnabled) { if (m_isSubcategoriesEnabled)
{
QStringList expanded = BitTorrent::Session::expandCategory(categoryName); QStringList expanded = BitTorrent::Session::expandCategory(categoryName);
if (expanded.count() > 1) if (expanded.count() > 1)
parent = findItem(expanded[expanded.count() - 2]); parent = findItem(expanded[expanded.count() - 2]);
@ -322,7 +327,8 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName)
void CategoryFilterModel::categoryRemoved(const QString &categoryName) void CategoryFilterModel::categoryRemoved(const QString &categoryName)
{ {
auto item = findItem(categoryName); auto item = findItem(categoryName);
if (item) { if (item)
{
QModelIndex i = index(item); QModelIndex i = index(item);
beginRemoveRows(i.parent(), i.row(), i.row()); beginRemoveRows(i.parent(), i.row(), i.row());
delete item; delete item;
@ -357,7 +363,8 @@ void CategoryFilterModel::torrentCategoryChanged(BitTorrent::TorrentHandle *cons
item->decreaseTorrentsCount(); item->decreaseTorrentsCount();
i = index(item); i = index(item);
while (i.isValid()) { while (i.isValid())
{
emit dataChanged(i, i); emit dataChanged(i, i);
i = parent(i); i = parent(i);
} }
@ -367,7 +374,8 @@ void CategoryFilterModel::torrentCategoryChanged(BitTorrent::TorrentHandle *cons
item->increaseTorrentsCount(); item->increaseTorrentsCount();
i = index(item); i = index(item);
while (i.isValid()) { while (i.isValid())
{
emit dataChanged(i, i); emit dataChanged(i, i);
i = parent(i); i = parent(i);
} }
@ -404,13 +412,17 @@ void CategoryFilterModel::populate()
, [](Torrent *torrent) { return torrent->category().isEmpty(); }))); , [](Torrent *torrent) { return torrent->category().isEmpty(); })));
using Torrent = BitTorrent::TorrentHandle; using Torrent = BitTorrent::TorrentHandle;
for (auto i = session->categories().cbegin(); i != session->categories().cend(); ++i) { for (auto i = session->categories().cbegin(); i != session->categories().cend(); ++i)
{
const QString &category = i.key(); const QString &category = i.key();
if (m_isSubcategoriesEnabled) { if (m_isSubcategoriesEnabled)
{
CategoryModelItem *parent = m_rootItem; CategoryModelItem *parent = m_rootItem;
for (const QString &subcat : asConst(session->expandCategory(category))) { for (const QString &subcat : asConst(session->expandCategory(category)))
{
const QString subcatName = shortName(subcat); const QString subcatName = shortName(subcat);
if (!parent->hasChild(subcatName)) { if (!parent->hasChild(subcatName))
{
new CategoryModelItem( new CategoryModelItem(
parent, subcatName parent, subcatName
, std::count_if(torrents.cbegin(), torrents.cend() , std::count_if(torrents.cbegin(), torrents.cend()
@ -419,7 +431,8 @@ void CategoryFilterModel::populate()
parent = parent->child(subcatName); parent = parent->child(subcatName);
} }
} }
else { else
{
new CategoryModelItem( new CategoryModelItem(
m_rootItem, category m_rootItem, category
, std::count_if(torrents.begin(), torrents.end() , std::count_if(torrents.begin(), torrents.end()
@ -437,7 +450,8 @@ CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
return m_rootItem->child(fullName); return m_rootItem->child(fullName);
CategoryModelItem *item = m_rootItem; CategoryModelItem *item = m_rootItem;
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(fullName))) { for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(fullName)))
{
const QString subcatName = shortName(subcat); const QString subcatName = shortName(subcat);
if (!item->hasChild(subcatName)) return nullptr; if (!item->hasChild(subcatName)) return nullptr;
item = item->child(subcatName); item = item->child(subcatName);

View file

@ -44,7 +44,8 @@ namespace
QString getCategoryFilter(const CategoryFilterProxyModel *const model, const QModelIndex &index) QString getCategoryFilter(const CategoryFilterProxyModel *const model, const QModelIndex &index)
{ {
QString categoryFilter; // Defaults to All QString categoryFilter; // Defaults to All
if (index.isValid()) { if (index.isValid())
{
if (!index.parent().isValid() && (index.row() == 1)) if (!index.parent().isValid() && (index.row() == 1))
categoryFilter = ""; // Uncategorized categoryFilter = ""; // Uncategorized
else if (index.parent().isValid() || (index.row() > 1)) else if (index.parent().isValid() || (index.row() > 1))
@ -115,8 +116,10 @@ void CategoryFilterWidget::showMenu(const QPoint &)
connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory); connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory);
const auto selectedRows = selectionModel()->selectedRows(); const auto selectedRows = selectionModel()->selectedRows();
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) { if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) { {
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
{
const QAction *addSubAct = menu->addAction( const QAction *addSubAct = menu->addAction(
UIThemeManager::instance()->getIcon("list-add") UIThemeManager::instance()->getIcon("list-add")
, tr("Add subcategory...")); , tr("Add subcategory..."));
@ -175,7 +178,8 @@ QSize CategoryFilterWidget::sizeHint() const
// otherwise widget will not correctly adjust the // otherwise widget will not correctly adjust the
// size when subcategories are used. // size when subcategories are used.
const QSize viewportSize {viewportSizeHint()}; const QSize viewportSize {viewportSizeHint()};
return { return
{
viewportSize.width(), viewportSize.width(),
viewportSize.height() + static_cast<int>(0.5 * sizeHintForRow(0)) viewportSize.height() + static_cast<int>(0.5 * sizeHintForRow(0))
}; };
@ -194,7 +198,8 @@ void CategoryFilterWidget::rowsInserted(const QModelIndex &parent, int start, in
// Expand all parents if the parent(s) of the node are not expanded. // Expand all parents if the parent(s) of the node are not expanded.
QModelIndex p = parent; QModelIndex p = parent;
while (p.isValid()) { while (p.isValid())
{
if (!isExpanded(p)) if (!isExpanded(p))
expand(p); expand(p);
p = model()->parent(p); p = model()->parent(p);
@ -221,7 +226,8 @@ void CategoryFilterWidget::editCategory()
void CategoryFilterWidget::removeCategory() void CategoryFilterWidget::removeCategory()
{ {
const auto selectedRows = selectionModel()->selectedRows(); const auto selectedRows = selectionModel()->selectedRows();
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) { if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
{
BitTorrent::Session::instance()->removeCategory( BitTorrent::Session::instance()->removeCategory(
static_cast<CategoryFilterProxyModel *>(model())->categoryName(selectedRows.first())); static_cast<CategoryFilterProxyModel *>(model())->categoryName(selectedRows.first()));
updateGeometry(); updateGeometry();
@ -231,7 +237,8 @@ void CategoryFilterWidget::removeCategory()
void CategoryFilterWidget::removeUnusedCategories() void CategoryFilterWidget::removeUnusedCategories()
{ {
auto session = BitTorrent::Session::instance(); auto session = BitTorrent::Session::instance();
for (const QString &category : asConst(session->categories().keys())) { for (const QString &category : asConst(session->categories().keys()))
{
if (model()->data(static_cast<CategoryFilterProxyModel *>(model())->index(category), Qt::UserRole) == 0) if (model()->data(static_cast<CategoryFilterProxyModel *>(model())->index(category), Qt::UserRole) == 0)
session->removeCategory(category); session->removeCategory(category);
} }

View file

@ -43,8 +43,10 @@ QList<QNetworkCookie> CookiesModel::cookies() const
QVariant CookiesModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant CookiesModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal)) { if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal))
switch (section) { {
switch (section)
{
case COL_DOMAIN: case COL_DOMAIN:
return tr("Domain"); return tr("Domain");
case COL_PATH: case COL_PATH:
@ -96,7 +98,8 @@ QVariant CookiesModel::data(const QModelIndex &index, int role) const
|| ((role != Qt::DisplayRole) && (role != Qt::EditRole))) || ((role != Qt::DisplayRole) && (role != Qt::EditRole)))
return {}; return {};
switch (index.column()) { switch (index.column())
{
case COL_DOMAIN: case COL_DOMAIN:
return m_cookies[index.row()].domain(); return m_cookies[index.row()].domain();
case COL_PATH: case COL_PATH:
@ -116,7 +119,8 @@ bool CookiesModel::setData(const QModelIndex &index, const QVariant &value, int
{ {
if (role != Qt::EditRole) return false; if (role != Qt::EditRole) return false;
switch (index.column()) { switch (index.column())
{
case COL_DOMAIN: case COL_DOMAIN:
m_cookies[index.row()].setDomain(value.toString()); m_cookies[index.row()].setDomain(value.toString());
break; break;

View file

@ -71,7 +71,8 @@ DownloadFromURLDialog::DownloadFromURLDialog(QWidget *parent)
const QVector<QStringRef> clipboardList = clipboardText.splitRef('\n'); const QVector<QStringRef> clipboardList = clipboardText.splitRef('\n');
QSet<QString> uniqueURLs; QSet<QString> uniqueURLs;
for (QStringRef strRef : clipboardList) { for (QStringRef strRef : clipboardList)
{
strRef = strRef.trimmed(); strRef = strRef.trimmed();
if (strRef.isEmpty()) continue; if (strRef.isEmpty()) continue;
@ -96,14 +97,16 @@ void DownloadFromURLDialog::downloadButtonClicked()
const QVector<QStringRef> urls = plainText.splitRef('\n'); const QVector<QStringRef> urls = plainText.splitRef('\n');
QSet<QString> uniqueURLs; QSet<QString> uniqueURLs;
for (QStringRef url : urls) { for (QStringRef url : urls)
{
url = url.trimmed(); url = url.trimmed();
if (url.isEmpty()) continue; if (url.isEmpty()) continue;
uniqueURLs << url.toString(); uniqueURLs << url.toString();
} }
if (uniqueURLs.isEmpty()) { if (uniqueURLs.isEmpty())
{
QMessageBox::warning(this, tr("No URL entered"), tr("Please type at least one URL.")); QMessageBox::warning(this, tr("No URL entered"), tr("Please type at least one URL."));
return; return;
} }

View file

@ -89,7 +89,8 @@ void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
// only show copy action if any of the row is selected // only show copy action if any of the row is selected
if (view->currentIndex().isValid()) { if (view->currentIndex().isValid())
{
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")); const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
connect(copyAct, &QAction::triggered, view, &LogListView::copySelection); connect(copyAct, &QAction::triggered, view, &LogListView::copySelection);
} }

View file

@ -115,7 +115,8 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::browseActionTriggered()
QString directory = q->currentDirectory().isEmpty() ? QDir::homePath() : q->currentDirectory(); QString directory = q->currentDirectory().isEmpty() ? QDir::homePath() : q->currentDirectory();
QString selectedPath; QString selectedPath;
switch (m_mode) { switch (m_mode)
{
case FileSystemPathEdit::Mode::FileOpen: case FileSystemPathEdit::Mode::FileOpen:
selectedPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), directory, filter); selectedPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), directory, filter);
break; break;
@ -139,7 +140,8 @@ QString FileSystemPathEdit::FileSystemPathEditPrivate::dialogCaptionOrDefault()
if (!m_dialogCaption.isEmpty()) if (!m_dialogCaption.isEmpty())
return m_dialogCaption; return m_dialogCaption;
switch (m_mode) { switch (m_mode)
{
case FileSystemPathEdit::Mode::FileOpen: case FileSystemPathEdit::Mode::FileOpen:
case FileSystemPathEdit::Mode::FileSave: case FileSystemPathEdit::Mode::FileSave:
return defaultDialogCaptionForFile.tr(); return defaultDialogCaptionForFile.tr();
@ -155,7 +157,8 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::modeChanged()
{ {
QStyle::StandardPixmap pixmap = QStyle::SP_DialogOpenButton; QStyle::StandardPixmap pixmap = QStyle::SP_DialogOpenButton;
bool showDirsOnly = false; bool showDirsOnly = false;
switch (m_mode) { switch (m_mode)
{
case FileSystemPathEdit::Mode::FileOpen: case FileSystemPathEdit::Mode::FileOpen:
case FileSystemPathEdit::Mode::FileSave: case FileSystemPathEdit::Mode::FileSave:
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -229,17 +232,21 @@ void FileSystemPathEdit::setFileNameFilter(const QString &val)
// extract file masks // extract file masks
const int openBracePos = val.indexOf(QLatin1Char('('), 0); const int openBracePos = val.indexOf(QLatin1Char('('), 0);
const int closeBracePos = val.indexOf(QLatin1Char(')'), openBracePos + 1); const int closeBracePos = val.indexOf(QLatin1Char(')'), openBracePos + 1);
if ((openBracePos > 0) && (closeBracePos > 0) && (closeBracePos > openBracePos + 2)) { if ((openBracePos > 0) && (closeBracePos > 0) && (closeBracePos > openBracePos + 2))
{
QString filterString = val.mid(openBracePos + 1, closeBracePos - openBracePos - 1); QString filterString = val.mid(openBracePos + 1, closeBracePos - openBracePos - 1);
if (filterString == QLatin1String("*")) { // no filters if (filterString == QLatin1String("*"))
{ // no filters
d->m_editor->setFilenameFilters({}); d->m_editor->setFilenameFilters({});
} }
else { else
{
QStringList filters = filterString.split(QLatin1Char(' '), QString::SkipEmptyParts); QStringList filters = filterString.split(QLatin1Char(' '), QString::SkipEmptyParts);
d->m_editor->setFilenameFilters(filters); d->m_editor->setFilenameFilters(filters);
} }
} }
else { else
{
d->m_editor->setFilenameFilters({}); d->m_editor->setFilenameFilters({});
} }
#endif #endif
@ -261,7 +268,8 @@ void FileSystemPathEdit::onPathEdited()
{ {
Q_D(FileSystemPathEdit); Q_D(FileSystemPathEdit);
QString newPath = selectedPath(); QString newPath = selectedPath();
if (newPath != d->m_lastSignaledPath) { if (newPath != d->m_lastSignaledPath)
{
emit selectedPathChanged(newPath); emit selectedPathChanged(newPath);
d->m_lastSignaledPath = newPath; d->m_lastSignaledPath = newPath;
d->m_editor->widget()->setToolTip(editWidgetText()); d->m_editor->widget()->setToolTip(editWidgetText());

View file

@ -115,7 +115,8 @@ QValidator::State Private::FileSystemPathValidator::validate(QString &input, int
// components.size() - 1 because when path ends with QDir::separator(), we will not see the last // components.size() - 1 because when path ends with QDir::separator(), we will not see the last
// character in the components array, yet everything past the one before the last delimiter // character in the components array, yet everything past the one before the last delimiter
// belongs to the last component // belongs to the last component
for (; (componentWithCursorIndex < components.size() - 1) && (pathLength < pos); ++componentWithCursorIndex) { for (; (componentWithCursorIndex < components.size() - 1) && (pathLength < pos); ++componentWithCursorIndex)
{
pathLength = components[componentWithCursorIndex].position() + components[componentWithCursorIndex].size(); pathLength = components[componentWithCursorIndex].position() + components[componentWithCursorIndex].size();
} }
@ -140,12 +141,14 @@ QValidator::State Private::FileSystemPathValidator::validate(const QString &path
if (pathComponents.empty()) if (pathComponents.empty())
return strict ? QValidator::Invalid : QValidator::Intermediate; return strict ? QValidator::Invalid : QValidator::Intermediate;
for (int i = firstComponentToTest; i < lastComponentToTest; ++i) { for (int i = firstComponentToTest; i < lastComponentToTest; ++i)
{
if (pathComponents[i].isEmpty()) continue; if (pathComponents[i].isEmpty()) continue;
QStringRef componentPath(&path, 0, pathComponents[i].position() + pathComponents[i].size()); QStringRef componentPath(&path, 0, pathComponents[i].position() + pathComponents[i].size());
m_lastTestResult = testPath(componentPath, false); m_lastTestResult = testPath(componentPath, false);
if (m_lastTestResult != TestResult::OK) { if (m_lastTestResult != TestResult::OK)
{
m_lastTestedPath = componentPath.toString(); m_lastTestedPath = componentPath.toString();
return strict ? QValidator::Invalid : QValidator::Intermediate; return strict ? QValidator::Invalid : QValidator::Intermediate;
} }
@ -155,7 +158,8 @@ QValidator::State Private::FileSystemPathValidator::validate(const QString &path
QStringRef componentPath(&path, 0, pathComponents[lastComponentToTest].position() QStringRef componentPath(&path, 0, pathComponents[lastComponentToTest].position()
+ pathComponents[lastComponentToTest].size()); + pathComponents[lastComponentToTest].size());
m_lastTestResult = testPath(componentPath, finalPath); m_lastTestResult = testPath(componentPath, finalPath);
if (m_lastTestResult != TestResult::OK) { if (m_lastTestResult != TestResult::OK)
{
m_lastTestedPath = componentPath.toString(); m_lastTestedPath = componentPath.toString();
return strict ? QValidator::Invalid : QValidator::Intermediate; return strict ? QValidator::Invalid : QValidator::Intermediate;
} }
@ -172,7 +176,8 @@ Private::FileSystemPathValidator::testPath(const QStringRef &path, bool pathIsCo
if ((!pathIsComplete || m_directoriesOnly) && !fi.isDir()) if ((!pathIsComplete || m_directoriesOnly) && !fi.isDir())
return TestResult::NotADir; return TestResult::NotADir;
if (pathIsComplete) { if (pathIsComplete)
{
if (!m_directoriesOnly && fi.isDir()) if (!m_directoriesOnly && fi.isDir())
return TestResult::NotAFile; return TestResult::NotAFile;
@ -249,27 +254,33 @@ QWidget *Private::FileLineEdit::widget()
void Private::FileLineEdit::keyPressEvent(QKeyEvent *e) void Private::FileLineEdit::keyPressEvent(QKeyEvent *e)
{ {
QLineEdit::keyPressEvent(e); QLineEdit::keyPressEvent(e);
if ((e->key() == Qt::Key_Space) && (e->modifiers() == Qt::CTRL)) { if ((e->key() == Qt::Key_Space) && (e->modifiers() == Qt::CTRL))
{
m_completerModel->setRootPath(QFileInfo(text()).absoluteDir().absolutePath()); m_completerModel->setRootPath(QFileInfo(text()).absoluteDir().absolutePath());
showCompletionPopup(); showCompletionPopup();
} }
auto *validator = qobject_cast<const FileSystemPathValidator *>(this->validator()); auto *validator = qobject_cast<const FileSystemPathValidator *>(this->validator());
if (validator) { if (validator)
{
FileSystemPathValidator::TestResult lastTestResult = validator->lastTestResult(); FileSystemPathValidator::TestResult lastTestResult = validator->lastTestResult();
QValidator::State lastState = validator->lastValidationState(); QValidator::State lastState = validator->lastValidationState();
if (lastTestResult == FileSystemPathValidator::TestResult::OK) { if (lastTestResult == FileSystemPathValidator::TestResult::OK)
{
delete m_warningAction; delete m_warningAction;
m_warningAction = nullptr; m_warningAction = nullptr;
} }
else { else
if (!m_warningAction) { {
if (!m_warningAction)
{
m_warningAction = new QAction(this); m_warningAction = new QAction(this);
addAction(m_warningAction, QLineEdit::TrailingPosition); addAction(m_warningAction, QLineEdit::TrailingPosition);
} }
} }
if (m_warningAction) { if (m_warningAction)
{
if (lastState == QValidator::Invalid) if (lastState == QValidator::Invalid)
m_warningAction->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical)); m_warningAction->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical));
else if (lastState == QValidator::Intermediate) else if (lastState == QValidator::Intermediate)
@ -284,7 +295,8 @@ void Private::FileLineEdit::contextMenuEvent(QContextMenuEvent *event)
QMenu *menu = createStandardContextMenu(); QMenu *menu = createStandardContextMenu();
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
if (m_browseAction) { if (m_browseAction)
{
menu->addSeparator(); menu->addSeparator();
menu->addAction(m_browseAction); menu->addAction(m_browseAction);
} }
@ -301,7 +313,8 @@ void Private::FileLineEdit::showCompletionPopup()
QString Private::FileLineEdit::warningText(FileSystemPathValidator::TestResult r) QString Private::FileLineEdit::warningText(FileSystemPathValidator::TestResult r)
{ {
using TestResult = FileSystemPathValidator::TestResult; using TestResult = FileSystemPathValidator::TestResult;
switch (r) { switch (r)
{
case TestResult::DoesNotExist: case TestResult::DoesNotExist:
return tr("'%1' does not exist"); return tr("'%1' does not exist");
case TestResult::NotADir: case TestResult::NotADir:

View file

@ -68,7 +68,8 @@ IPSubnetWhitelistOptionsDialog::~IPSubnetWhitelistOptionsDialog()
void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted() void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
{ {
if (m_modified) { if (m_modified)
{
// save to session // save to session
QStringList subnets; QStringList subnets;
// Operate on the m_sortFilter to grab the strings in sorted order // Operate on the m_sortFilter to grab the strings in sorted order
@ -77,7 +78,8 @@ void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
Preferences::instance()->setWebUiAuthSubnetWhitelist(subnets); Preferences::instance()->setWebUiAuthSubnetWhitelist(subnets);
QDialog::accept(); QDialog::accept();
} }
else { else
{
QDialog::reject(); QDialog::reject();
} }
} }
@ -86,7 +88,8 @@ void IPSubnetWhitelistOptionsDialog::on_buttonWhitelistIPSubnet_clicked()
{ {
bool ok = false; bool ok = false;
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text(), &ok); const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text(), &ok);
if (!ok) { if (!ok)
{
QMessageBox::critical(this, tr("Error"), tr("The entered subnet is invalid.")); QMessageBox::critical(this, tr("Error"), tr("The entered subnet is invalid."));
return; return;
} }

View file

@ -47,7 +47,8 @@ void LineEdit::resizeEvent(QResizeEvent *e)
void LineEdit::keyPressEvent(QKeyEvent *event) void LineEdit::keyPressEvent(QKeyEvent *event)
{ {
if ((event->modifiers() == Qt::NoModifier) && (event->key() == Qt::Key_Escape)) { if ((event->modifiers() == Qt::NoModifier) && (event->key() == Qt::Key_Escape))
{
clear(); clear();
} }
QLineEdit::keyPressEvent(event); QLineEdit::keyPressEvent(event);

View file

@ -97,7 +97,8 @@ QVariant BaseLogModel::data(const QModelIndex &index, const int role) const
return {}; return {};
const Message &message = m_messages[messageIndex]; const Message &message = m_messages[messageIndex];
switch (role) { switch (role)
{
case TimeRole: case TimeRole:
return message.time(); return message.time();
case MessageRole: case MessageRole:
@ -117,7 +118,8 @@ void BaseLogModel::addNewMessage(const BaseLogModel::Message &message)
{ {
// if row is inserted on filled up buffer, the size will not change // if row is inserted on filled up buffer, the size will not change
// but because of calling of beginInsertRows function we'll have ghost rows. // but because of calling of beginInsertRows function we'll have ghost rows.
if (m_messages.size() == MAX_VISIBLE_MESSAGES) { if (m_messages.size() == MAX_VISIBLE_MESSAGES)
{
const int lastMessage = m_messages.size() - 1; const int lastMessage = m_messages.size() - 1;
beginRemoveRows(QModelIndex(), lastMessage, lastMessage); beginRemoveRows(QModelIndex(), lastMessage, lastMessage);
m_messages.pop_back(); m_messages.pop_back();
@ -138,7 +140,8 @@ void BaseLogModel::reset()
LogMessageModel::LogMessageModel(QObject *parent) LogMessageModel::LogMessageModel(QObject *parent)
: BaseLogModel(parent) : BaseLogModel(parent)
, m_foregroundForMessageTypes { , m_foregroundForMessageTypes
{
{Log::NORMAL, UIThemeManager::instance()->getColor(QLatin1String("Log.Normal"), QApplication::palette().color(QPalette::WindowText))}, {Log::NORMAL, UIThemeManager::instance()->getColor(QLatin1String("Log.Normal"), QApplication::palette().color(QPalette::WindowText))},
{Log::INFO, UIThemeManager::instance()->getColor(QLatin1String("Log.Info"), Qt::blue)}, {Log::INFO, UIThemeManager::instance()->getColor(QLatin1String("Log.Info"), Qt::blue)},
{Log::WARNING, UIThemeManager::instance()->getColor(QLatin1String("Log.Warning"), QColor {255, 165, 0})}, // orange {Log::WARNING, UIThemeManager::instance()->getColor(QLatin1String("Log.Warning"), QColor {255, 165, 0})}, // orange

View file

@ -281,8 +281,10 @@ MainWindow::MainWindow(QWidget *parent)
m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos); m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
for (QAction *action : asConst(m_ui->toolBar->actions())) { for (QAction *action : asConst(m_ui->toolBar->actions()))
if (action->isSeparator()) { {
if (action->isSeparator())
{
QWidget *spacer = new QWidget(this); QWidget *spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
spacer->setMinimumWidth(16); spacer->setMinimumWidth(16);
@ -406,30 +408,38 @@ MainWindow::MainWindow(QWidget *parent)
readSettings(); readSettings();
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
if (m_systrayIcon) { if (m_systrayIcon)
if (!(pref->startMinimized() || m_uiLocked)) { {
if (!(pref->startMinimized() || m_uiLocked))
{
show(); show();
activateWindow(); activateWindow();
raise(); raise();
} }
else if (pref->startMinimized()) { else if (pref->startMinimized())
{
showMinimized(); showMinimized();
if (pref->minimizeToTray()) { if (pref->minimizeToTray())
{
hide(); hide();
if (!pref->minimizeToTrayNotified()) { if (!pref->minimizeToTrayNotified())
{
showNotificationBaloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again.")); showNotificationBaloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
pref->setMinimizeToTrayNotified(true); pref->setMinimizeToTrayNotified(true);
} }
} }
} }
} }
else { else
{
#endif #endif
// Make sure the Window is visible if we don't have a tray icon // Make sure the Window is visible if we don't have a tray icon
if (pref->startMinimized()) { if (pref->startMinimized())
{
showMinimized(); showMinimized();
} }
else { else
{
show(); show();
activateWindow(); activateWindow();
raise(); raise();
@ -456,14 +466,17 @@ MainWindow::MainWindow(QWidget *parent)
qDebug("GUI Built"); qDebug("GUI Built");
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet())) { if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet()))
{
if (QMessageBox::question(this, tr("Torrent file association"), if (QMessageBox::question(this, tr("Torrent file association"),
tr("qBittorrent is not the default application to open torrent files or Magnet links.\nDo you want to associate qBittorrent to torrent files and Magnet links?"), tr("qBittorrent is not the default application to open torrent files or Magnet links.\nDo you want to associate qBittorrent to torrent files and Magnet links?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
{
Preferences::setTorrentFileAssoc(true); Preferences::setTorrentFileAssoc(true);
Preferences::setMagnetLinkAssoc(true); Preferences::setMagnetLinkAssoc(true);
} }
else { else
{
pref->setNeverCheckFileAssoc(); pref->setNeverCheckFileAssoc();
} }
} }
@ -567,7 +580,8 @@ void MainWindow::addToolbarContextMenu()
const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition()); const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle)) if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
m_ui->toolBar->setToolButtonStyle(buttonStyle); m_ui->toolBar->setToolButtonStyle(buttonStyle);
switch (buttonStyle) { switch (buttonStyle)
{
case Qt::ToolButtonIconOnly: case Qt::ToolButtonIconOnly:
iconsOnly->setChecked(true); iconsOnly->setChecked(true);
break; break;
@ -635,7 +649,8 @@ bool MainWindow::defineUILockPassword()
if (!ok) if (!ok)
return false; return false;
if (newPassword.size() < 3) { if (newPassword.size() < 3)
{
QMessageBox::warning(this, tr("Invalid password"), tr("The password should contain at least 3 characters")); QMessageBox::warning(this, tr("Invalid password"), tr("The password should contain at least 3 characters"));
return false; return false;
} }
@ -657,7 +672,8 @@ void MainWindow::on_actionLock_triggered()
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
// Check if there is a password // Check if there is a password
if (pref->getUILockPassword().isEmpty()) { if (pref->getUILockPassword().isEmpty())
{
if (!defineUILockPassword()) if (!defineUILockPassword())
return; return;
} }
@ -676,9 +692,11 @@ void MainWindow::handleRSSUnreadCountUpdated(int count)
void MainWindow::displayRSSTab(bool enable) void MainWindow::displayRSSTab(bool enable)
{ {
if (enable) { if (enable)
{
// RSS tab // RSS tab
if (!m_rssWidget) { if (!m_rssWidget)
{
m_rssWidget = new RSSWidget(m_tabs); m_rssWidget = new RSSWidget(m_tabs);
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated); connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -689,7 +707,8 @@ void MainWindow::displayRSSTab(bool enable)
#endif #endif
} }
} }
else { else
{
delete m_rssWidget; delete m_rssWidget;
} }
} }
@ -714,9 +733,11 @@ void MainWindow::showFilterContextMenu(const QPoint &)
void MainWindow::displaySearchTab(bool enable) void MainWindow::displaySearchTab(bool enable)
{ {
Preferences::instance()->setSearchEnabled(enable); Preferences::instance()->setSearchEnabled(enable);
if (enable) { if (enable)
{
// RSS tab // RSS tab
if (!m_searchWidget) { if (!m_searchWidget)
{
m_searchWidget = new SearchWidget(this); m_searchWidget = new SearchWidget(this);
m_tabs->insertTab(1, m_searchWidget, m_tabs->insertTab(1, m_searchWidget,
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
@ -725,7 +746,8 @@ void MainWindow::displaySearchTab(bool enable)
tr("Search")); tr("Search"));
} }
} }
else { else
{
delete m_searchWidget; delete m_searchWidget;
} }
} }
@ -751,7 +773,8 @@ void MainWindow::tabChanged(int newTab)
Q_UNUSED(newTab); Q_UNUSED(newTab);
// We cannot rely on the index newTab // We cannot rely on the index newTab
// because the tab order is undetermined now // because the tab order is undetermined now
if (m_tabs->currentWidget() == m_splitter) { if (m_tabs->currentWidget() == m_splitter)
{
qDebug("Changed tab to transfer list, refreshing the list"); qDebug("Changed tab to transfer list, refreshing the list");
m_propertiesWidget->loadDynamicData(); m_propertiesWidget->loadDynamicData();
m_searchFilterAction->setVisible(true); m_searchFilterAction->setVisible(true);
@ -759,7 +782,8 @@ void MainWindow::tabChanged(int newTab)
} }
m_searchFilterAction->setVisible(false); m_searchFilterAction->setVisible(false);
if (m_tabs->currentWidget() == m_searchWidget) { if (m_tabs->currentWidget() == m_searchWidget)
{
qDebug("Changed tab to search engine, giving focus to search input"); qDebug("Changed tab to search engine, giving focus to search input");
m_searchWidget->giveFocusToSearchInput(); m_searchWidget->giveFocusToSearchInput();
} }
@ -815,8 +839,10 @@ void MainWindow::readSettings()
void MainWindow::balloonClicked() void MainWindow::balloonClicked()
{ {
if (isHidden()) { if (isHidden())
if (m_uiLocked) { {
if (m_uiLocked)
{
// Ask for UI lock password // Ask for UI lock password
if (!unlockUI()) if (!unlockUI())
return; return;
@ -916,7 +942,8 @@ void MainWindow::displayTransferTab() const
void MainWindow::displaySearchTab() void MainWindow::displaySearchTab()
{ {
if (!m_searchWidget) { if (!m_searchWidget)
{
m_ui->actionSearchWidget->setChecked(true); m_ui->actionSearchWidget->setChecked(true);
displaySearchTab(true); displaySearchTab(true);
} }
@ -926,7 +953,8 @@ void MainWindow::displaySearchTab()
void MainWindow::displayRSSTab() void MainWindow::displayRSSTab()
{ {
if (!m_rssWidget) { if (!m_rssWidget)
{
m_ui->actionRSSReader->setChecked(true); m_ui->actionRSSReader->setChecked(true);
displayRSSTab(true); displayRSSTab(true);
} }
@ -936,7 +964,8 @@ void MainWindow::displayRSSTab()
void MainWindow::displayExecutionLogTab() void MainWindow::displayExecutionLogTab()
{ {
if (!m_executionLog) { if (!m_executionLog)
{
m_ui->actionExecutionLogs->setChecked(true); m_ui->actionExecutionLogs->setChecked(true);
on_actionExecutionLogs_triggered(true); on_actionExecutionLogs_triggered(true);
} }
@ -988,7 +1017,8 @@ void MainWindow::on_actionSetGlobalUploadLimit_triggered()
const long newLimit = SpeedLimitDialog::askSpeedLimit( const long newLimit = SpeedLimitDialog::askSpeedLimit(
this, &ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit()); this, &ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit());
if (ok) { if (ok)
{
qDebug("Setting global upload rate limit to %.1fKb/s", newLimit / 1024.); qDebug("Setting global upload rate limit to %.1fKb/s", newLimit / 1024.);
session->setUploadSpeedLimit(newLimit); session->setUploadSpeedLimit(newLimit);
} }
@ -1003,7 +1033,8 @@ void MainWindow::on_actionSetGlobalDownloadLimit_triggered()
const long newLimit = SpeedLimitDialog::askSpeedLimit( const long newLimit = SpeedLimitDialog::askSpeedLimit(
this, &ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit()); this, &ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit());
if (ok) { if (ok)
{
qDebug("Setting global download rate limit to %.1fKb/s", newLimit / 1024.); qDebug("Setting global download rate limit to %.1fKb/s", newLimit / 1024.);
session->setDownloadSpeedLimit(newLimit); session->setDownloadSpeedLimit(newLimit);
} }
@ -1059,7 +1090,8 @@ bool MainWindow::unlockUI()
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
const QByteArray secret = pref->getUILockPassword(); const QByteArray secret = pref->getUILockPassword();
if (!Utils::Password::PBKDF2::verify(secret, password)) { if (!Utils::Password::PBKDF2::verify(secret, password))
{
QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid")); QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
return false; return false;
} }
@ -1085,9 +1117,11 @@ void MainWindow::notifyOfUpdate(const QString &)
// Toggle Main window visibility // Toggle Main window visibility
void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason) void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason)
{ {
switch (reason) { switch (reason)
{
case QSystemTrayIcon::Trigger: case QSystemTrayIcon::Trigger:
if (isHidden()) { if (isHidden())
{
if (m_uiLocked && !unlockUI()) // Ask for UI lock password if (m_uiLocked && !unlockUI()) // Ask for UI lock password
return; return;
@ -1099,7 +1133,8 @@ void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason
raise(); raise();
activateWindow(); activateWindow();
} }
else { else
{
hide(); hide();
} }
break; break;
@ -1138,7 +1173,8 @@ void MainWindow::showEvent(QShowEvent *e)
e->accept(); e->accept();
// Make sure the window is initially centered // Make sure the window is initially centered
if (!m_posInitialized) { if (!m_posInitialized)
{
move(Utils::Gui::screenCenter(this)); move(Utils::Gui::screenCenter(this));
m_posInitialized = true; m_posInitialized = true;
} }
@ -1146,14 +1182,17 @@ void MainWindow::showEvent(QShowEvent *e)
void MainWindow::keyPressEvent(QKeyEvent *event) void MainWindow::keyPressEvent(QKeyEvent *event)
{ {
if (event->matches(QKeySequence::Paste)) { if (event->matches(QKeySequence::Paste))
{
const QMimeData *mimeData {QGuiApplication::clipboard()->mimeData()}; const QMimeData *mimeData {QGuiApplication::clipboard()->mimeData()};
if (mimeData->hasText()) { if (mimeData->hasText())
{
const bool useTorrentAdditionDialog {AddNewTorrentDialog::isEnabled()}; const bool useTorrentAdditionDialog {AddNewTorrentDialog::isEnabled()};
const QStringList lines {mimeData->text().split('\n', QString::SkipEmptyParts)}; const QStringList lines {mimeData->text().split('\n', QString::SkipEmptyParts)};
for (QString line : lines) { for (QString line : lines)
{
line = line.trimmed(); line = line.trimmed();
if (!isTorrentLink(line)) if (!isTorrentLink(line))
@ -1177,17 +1216,20 @@ void MainWindow::closeEvent(QCloseEvent *e)
{ {
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (!m_forceExit) { if (!m_forceExit)
{
hide(); hide();
e->accept(); e->accept();
return; return;
} }
#else #else
const bool goToSystrayOnExit = pref->closeToTray(); const bool goToSystrayOnExit = pref->closeToTray();
if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden()) { if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden())
{
e->ignore(); e->ignore();
QTimer::singleShot(0, this, &QWidget::hide); QTimer::singleShot(0, this, &QWidget::hide);
if (!pref->closeToTrayNotified()) { if (!pref->closeToTrayNotified())
{
showNotificationBaloon(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again.")); showNotificationBaloon(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
pref->setCloseToTrayNotified(true); pref->setCloseToTrayNotified(true);
} }
@ -1195,8 +1237,10 @@ void MainWindow::closeEvent(QCloseEvent *e)
} }
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) { if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents())
if (e->spontaneous() || m_forceExit) { {
if (e->spontaneous() || m_forceExit)
{
if (!isVisible()) if (!isVisible())
show(); show();
QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"), QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
@ -1208,7 +1252,8 @@ void MainWindow::closeEvent(QCloseEvent *e)
QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole); QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
confirmBox.setDefaultButton(noBtn); confirmBox.setDefaultButton(noBtn);
confirmBox.exec(); confirmBox.exec();
if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn)) { if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
{
// Cancel exit // Cancel exit
e->ignore(); e->ignore();
m_forceExit = false; m_forceExit = false;
@ -1242,7 +1287,8 @@ void MainWindow::on_actionCreateTorrent_triggered()
void MainWindow::createTorrentTriggered(const QString &path) void MainWindow::createTorrentTriggered(const QString &path)
{ {
if (m_createTorrentDlg) { if (m_createTorrentDlg)
{
m_createTorrentDlg->updateInputPath(path); m_createTorrentDlg->updateInputPath(path);
m_createTorrentDlg->activateWindow(); m_createTorrentDlg->activateWindow();
} }
@ -1253,29 +1299,37 @@ void MainWindow::createTorrentTriggered(const QString &path)
bool MainWindow::event(QEvent *e) bool MainWindow::event(QEvent *e)
{ {
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
switch (e->type()) { switch (e->type())
case QEvent::WindowStateChange: { {
case QEvent::WindowStateChange:
{
qDebug("Window change event"); qDebug("Window change event");
// Now check to see if the window is minimised // Now check to see if the window is minimised
if (isMinimized()) { if (isMinimized())
{
qDebug("minimisation"); qDebug("minimisation");
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
if (m_systrayIcon && pref->minimizeToTray()) { if (m_systrayIcon && pref->minimizeToTray())
{
qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr); qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
// Check if there is a modal window // Check if there is a modal window
bool hasModalWindow = false; bool hasModalWindow = false;
for (QWidget *widget : asConst(QApplication::allWidgets())) { for (QWidget *widget : asConst(QApplication::allWidgets()))
if (widget->isModal()) { {
if (widget->isModal())
{
hasModalWindow = true; hasModalWindow = true;
break; break;
} }
} }
// Iconify if there is no modal window // Iconify if there is no modal window
if (!hasModalWindow) { if (!hasModalWindow)
{
qDebug("Minimize to Tray enabled, hiding!"); qDebug("Minimize to Tray enabled, hiding!");
e->ignore(); e->ignore();
QTimer::singleShot(0, this, &QWidget::hide); QTimer::singleShot(0, this, &QWidget::hide);
if (!pref->minimizeToTrayNotified()) { if (!pref->minimizeToTrayNotified())
{
showNotificationBaloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again.")); showNotificationBaloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
pref->setMinimizeToTrayNotified(true); pref->setMinimizeToTrayNotified(true);
} }
@ -1285,7 +1339,8 @@ bool MainWindow::event(QEvent *e)
} }
break; break;
} }
case QEvent::ToolBarChange: { case QEvent::ToolBarChange:
{
qDebug("MAC: Received a toolbar change event!"); qDebug("MAC: Received a toolbar change event!");
bool ret = QMainWindow::event(e); bool ret = QMainWindow::event(e);
@ -1309,8 +1364,10 @@ void MainWindow::dropEvent(QDropEvent *event)
// remove scheme // remove scheme
QStringList files; QStringList files;
if (event->mimeData()->hasUrls()) { if (event->mimeData()->hasUrls())
for (const QUrl &url : asConst(event->mimeData()->urls())) { {
for (const QUrl &url : asConst(event->mimeData()->urls()))
{
if (url.isEmpty()) if (url.isEmpty())
continue; continue;
@ -1319,13 +1376,15 @@ void MainWindow::dropEvent(QDropEvent *event)
: url.toString()); : url.toString());
} }
} }
else { else
{
files = event->mimeData()->text().split('\n'); files = event->mimeData()->text().split('\n');
} }
// differentiate ".torrent" files/links & magnet links from others // differentiate ".torrent" files/links & magnet links from others
QStringList torrentFiles, otherFiles; QStringList torrentFiles, otherFiles;
for (const QString &file : asConst(files)) { for (const QString &file : asConst(files))
{
if (isTorrentLink(file)) if (isTorrentLink(file))
torrentFiles << file; torrentFiles << file;
else else
@ -1334,7 +1393,8 @@ void MainWindow::dropEvent(QDropEvent *event)
// Download torrents // Download torrents
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
for (const QString &file : asConst(torrentFiles)) { for (const QString &file : asConst(torrentFiles))
{
if (useTorrentAdditionDialog) if (useTorrentAdditionDialog)
AddNewTorrentDialog::show(file, this); AddNewTorrentDialog::show(file, this);
else else
@ -1343,7 +1403,8 @@ void MainWindow::dropEvent(QDropEvent *event)
if (!torrentFiles.isEmpty()) return; if (!torrentFiles.isEmpty()) return;
// Create torrent // Create torrent
for (const QString &file : asConst(otherFiles)) { for (const QString &file : asConst(otherFiles))
{
createTorrentTriggered(file); createTorrentTriggered(file);
// currently only handle the first entry // currently only handle the first entry
@ -1370,7 +1431,8 @@ static bool dockClickHandler(id self, SEL cmd, ...)
Q_UNUSED(self) Q_UNUSED(self)
Q_UNUSED(cmd) Q_UNUSED(cmd)
if (dockMainWindowHandle && !dockMainWindowHandle->isVisible()) { if (dockMainWindowHandle && !dockMainWindowHandle->isVisible())
{
dockMainWindowHandle->activate(); dockMainWindowHandle->activate();
} }
@ -1407,7 +1469,8 @@ void MainWindow::on_actionOpen_triggered()
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
for (const QString &file : pathsList) { for (const QString &file : pathsList)
{
if (useTorrentAdditionDialog) if (useTorrentAdditionDialog)
AddNewTorrentDialog::show(file, this); AddNewTorrentDialog::show(file, this);
else else
@ -1422,7 +1485,8 @@ void MainWindow::on_actionOpen_triggered()
void MainWindow::activate() void MainWindow::activate()
{ {
if (!m_uiLocked || unlockUI()) { if (!m_uiLocked || unlockUI())
{
show(); show();
activateWindow(); activateWindow();
raise(); raise();
@ -1436,11 +1500,13 @@ void MainWindow::optionsSaved()
void MainWindow::showStatusBar(bool show) void MainWindow::showStatusBar(bool show)
{ {
if (!show) { if (!show)
{
// Remove status bar // Remove status bar
setStatusBar(nullptr); setStatusBar(nullptr);
} }
else if (!m_statusBar) { else if (!m_statusBar)
{
// Create status bar // Create status bar
m_statusBar = new StatusBar; m_statusBar = new StatusBar;
connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings); connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
@ -1458,26 +1524,33 @@ void MainWindow::loadPreferences(const bool configureSession)
#else #else
const bool newSystrayIntegration = pref->systrayIntegration(); const bool newSystrayIntegration = pref->systrayIntegration();
m_ui->actionLock->setVisible(newSystrayIntegration); m_ui->actionLock->setVisible(newSystrayIntegration);
if (newSystrayIntegration != (m_systrayIcon != nullptr)) { if (newSystrayIntegration != (m_systrayIcon != nullptr))
if (newSystrayIntegration) { {
if (newSystrayIntegration)
{
// create the trayicon // create the trayicon
if (!QSystemTrayIcon::isSystemTrayAvailable()) { if (!QSystemTrayIcon::isSystemTrayAvailable())
if (!configureSession) { // Program startup {
if (!configureSession)
{ // Program startup
m_systrayCreator = new QTimer(this); m_systrayCreator = new QTimer(this);
connect(m_systrayCreator.data(), &QTimer::timeout, this, &MainWindow::createSystrayDelayed); connect(m_systrayCreator.data(), &QTimer::timeout, this, &MainWindow::createSystrayDelayed);
m_systrayCreator->setSingleShot(true); m_systrayCreator->setSingleShot(true);
m_systrayCreator->start(2000); m_systrayCreator->start(2000);
qDebug("Info: System tray is unavailable, trying again later."); qDebug("Info: System tray is unavailable, trying again later.");
} }
else { else
{
qDebug("Warning: System tray is unavailable."); qDebug("Warning: System tray is unavailable.");
} }
} }
else { else
{
createTrayIcon(); createTrayIcon();
} }
} }
else { else
{
// Destroy trayicon // Destroy trayicon
delete m_systrayIcon; delete m_systrayIcon;
delete m_trayIconMenu; delete m_trayIconMenu;
@ -1488,10 +1561,12 @@ void MainWindow::loadPreferences(const bool configureSession)
m_systrayIcon->setIcon(getSystrayIcon()); m_systrayIcon->setIcon(getSystrayIcon());
#endif #endif
// General // General
if (pref->isToolbarDisplayed()) { if (pref->isToolbarDisplayed())
{
m_ui->toolBar->setVisible(true); m_ui->toolBar->setVisible(true);
} }
else { else
{
// Clear search filter before hiding the top toolbar // Clear search filter before hiding the top toolbar
m_searchFilter->clear(); m_searchFilter->clear();
m_ui->toolBar->setVisible(false); m_ui->toolBar->setVisible(false);
@ -1499,13 +1574,16 @@ void MainWindow::loadPreferences(const bool configureSession)
showStatusBar(pref->isStatusbarDisplayed()); showStatusBar(pref->isStatusbarDisplayed());
if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding()) { if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding())
if (!m_preventTimer->isActive()) { {
if (!m_preventTimer->isActive())
{
updatePowerManagementState(); updatePowerManagementState();
m_preventTimer->start(PREVENT_SUSPEND_INTERVAL); m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
} }
} }
else { else
{
m_preventTimer->stop(); m_preventTimer->stop();
m_pwr->setActivityState(false); m_pwr->setActivityState(false);
} }
@ -1516,8 +1594,10 @@ void MainWindow::loadPreferences(const bool configureSession)
m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors()); m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
// Queueing System // Queueing System
if (BitTorrent::Session::instance()->isQueueingSystemEnabled()) { if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
if (!m_ui->actionDecreaseQueuePos->isVisible()) { {
if (!m_ui->actionDecreaseQueuePos->isVisible())
{
m_transferListWidget->hideQueuePosColumn(false); m_transferListWidget->hideQueuePosColumn(false);
m_ui->actionDecreaseQueuePos->setVisible(true); m_ui->actionDecreaseQueuePos->setVisible(true);
m_ui->actionIncreaseQueuePos->setVisible(true); m_ui->actionIncreaseQueuePos->setVisible(true);
@ -1529,8 +1609,10 @@ void MainWindow::loadPreferences(const bool configureSession)
m_queueSeparatorMenu->setVisible(true); m_queueSeparatorMenu->setVisible(true);
} }
} }
else { else
if (m_ui->actionDecreaseQueuePos->isVisible()) { {
if (m_ui->actionDecreaseQueuePos->isVisible())
{
m_transferListWidget->hideQueuePosColumn(true); m_transferListWidget->hideQueuePosColumn(true);
m_ui->actionDecreaseQueuePos->setVisible(false); m_ui->actionDecreaseQueuePos->setVisible(false);
m_ui->actionIncreaseQueuePos->setVisible(false); m_ui->actionIncreaseQueuePos->setVisible(false);
@ -1547,11 +1629,13 @@ void MainWindow::loadPreferences(const bool configureSession)
m_propertiesWidget->reloadPreferences(); m_propertiesWidget->reloadPreferences();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) { if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled)
{
m_wasUpdateCheckEnabled = true; m_wasUpdateCheckEnabled = true;
checkProgramUpdate(); checkProgramUpdate();
} }
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled) { else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled)
{
m_wasUpdateCheckEnabled = false; m_wasUpdateCheckEnabled = false;
m_programUpdateTimer->stop(); m_programUpdateTimer->stop();
} }
@ -1566,15 +1650,18 @@ void MainWindow::reloadSessionStats()
// update global information // update global information
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (status.payloadDownloadRate > 0) { if (status.payloadDownloadRate > 0)
{
QtMac::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds") QtMac::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate))); .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
} }
else if (!QtMac::badgeLabelText().isEmpty()) { else if (!QtMac::badgeLabelText().isEmpty())
{
QtMac::setBadgeLabelText(""); QtMac::setBadgeLabelText("");
} }
#else #else
if (m_systrayIcon) { if (m_systrayIcon)
{
const QString toolTip = QString::fromLatin1("%1\n%2").arg( const QString toolTip = QString::fromLatin1("%1\n%2").arg(
tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)) tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
, tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true))); , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
@ -1582,7 +1669,8 @@ void MainWindow::reloadSessionStats()
} }
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
if (m_displaySpeedInTitle) { if (m_displaySpeedInTitle)
{
setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version") setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true) .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
, Utils::Misc::friendlyUnit(status.payloadUploadRate, true) , Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
@ -1592,7 +1680,8 @@ void MainWindow::reloadSessionStats()
void MainWindow::reloadTorrentStats(const QVector<BitTorrent::TorrentHandle *> &torrents) void MainWindow::reloadTorrentStats(const QVector<BitTorrent::TorrentHandle *> &torrents)
{ {
if (currentTabWidget() == m_transferListWidget) { if (currentTabWidget() == m_transferListWidget)
{
if (torrents.contains(m_propertiesWidget->getCurrentTorrent())) if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
m_propertiesWidget->loadDynamicData(); m_propertiesWidget->loadDynamicData();
} }
@ -1638,7 +1727,8 @@ void MainWindow::showNotificationBaloon(const QString &title, const QString &msg
void MainWindow::downloadFromURLList(const QStringList &urlList) void MainWindow::downloadFromURLList(const QStringList &urlList)
{ {
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled(); const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
for (const QString &url : urlList) { for (const QString &url : urlList)
{
if (useTorrentAdditionDialog) if (useTorrentAdditionDialog)
AddNewTorrentDialog::show(url, this); AddNewTorrentDialog::show(url, this);
else else
@ -1656,19 +1746,23 @@ void MainWindow::downloadFromURLList(const QStringList &urlList)
void MainWindow::createSystrayDelayed() void MainWindow::createSystrayDelayed()
{ {
static int timeout = 20; static int timeout = 20;
if (QSystemTrayIcon::isSystemTrayAvailable()) { if (QSystemTrayIcon::isSystemTrayAvailable())
{
// Ok, systray integration is now supported // Ok, systray integration is now supported
// Create systray icon // Create systray icon
createTrayIcon(); createTrayIcon();
delete m_systrayCreator; delete m_systrayCreator;
} }
else { else
if (timeout) { {
if (timeout)
{
// Retry a bit later // Retry a bit later
m_systrayCreator->start(2000); m_systrayCreator->start(2000);
--timeout; --timeout;
} }
else { else
{
// Timed out, apparently system really does not // Timed out, apparently system really does not
// support systray icon // support systray icon
delete m_systrayCreator; delete m_systrayCreator;
@ -1780,11 +1874,13 @@ void MainWindow::on_actionRSSReader_triggered()
void MainWindow::on_actionSearchWidget_triggered() void MainWindow::on_actionSearchWidget_triggered()
{ {
if (!m_hasPython && m_ui->actionSearchWidget->isChecked()) { if (!m_hasPython && m_ui->actionSearchWidget->isChecked())
{
const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo(); const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
// Not installed // Not installed
if (!pyInfo.isValid()) { if (!pyInfo.isValid())
{
m_ui->actionSearchWidget->setChecked(false); m_ui->actionSearchWidget->setChecked(false);
Preferences::instance()->setSearchEnabled(false); Preferences::instance()->setSearchEnabled(false);
@ -1802,7 +1898,8 @@ void MainWindow::on_actionSearchWidget_triggered()
} }
// Check version requirement // Check version requirement
if (!pyInfo.isSupportedVersion()) { if (!pyInfo.isSupportedVersion())
{
m_ui->actionSearchWidget->setChecked(false); m_ui->actionSearchWidget->setChecked(false);
Preferences::instance()->setSearchEnabled(false); Preferences::instance()->setSearchEnabled(false);
@ -1839,7 +1936,8 @@ void MainWindow::on_actionSearchWidget_triggered()
// an url // an url
void MainWindow::on_actionDownloadFromURL_triggered() void MainWindow::on_actionDownloadFromURL_triggered()
{ {
if (!m_downloadFromURLDialog) { if (!m_downloadFromURLDialog)
{
m_downloadFromURLDialog = new DownloadFromURLDialog(this); m_downloadFromURLDialog = new DownloadFromURLDialog(this);
connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList); connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
} }
@ -1849,19 +1947,22 @@ void MainWindow::on_actionDownloadFromURL_triggered()
void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser) void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser)
{ {
QMessageBox::StandardButton answer = QMessageBox::Yes; QMessageBox::StandardButton answer = QMessageBox::Yes;
if (updateAvailable) { if (updateAvailable)
{
answer = QMessageBox::question(this, tr("qBittorrent Update Available") answer = QMessageBox::question(this, tr("qBittorrent Update Available")
, tr("A new version is available.") + "<br/>" , tr("A new version is available.") + "<br/>"
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>" + tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog...")) + QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (answer == QMessageBox::Yes) { if (answer == QMessageBox::Yes)
{
// The user want to update, let's download the update // The user want to update, let's download the update
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender()); ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
updater->updateProgram(); updater->updateProgram();
} }
} }
else if (invokedByUser) { else if (invokedByUser)
{
QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"), QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"),
tr("No updates available.\nYou are already using the latest version.")); tr("No updates available.\nYou are already using the latest version."));
} }
@ -1899,7 +2000,8 @@ void MainWindow::minimizeWindow()
void MainWindow::on_actionExecutionLogs_triggered(bool checked) void MainWindow::on_actionExecutionLogs_triggered(bool checked)
{ {
if (checked) { if (checked)
{
Q_ASSERT(!m_executionLog); Q_ASSERT(!m_executionLog);
m_executionLog = new ExecutionLogWidget(static_cast<Log::MsgType>(executionLogMsgTypes()), m_tabs); m_executionLog = new ExecutionLogWidget(static_cast<Log::MsgType>(executionLogMsgTypes()), m_tabs);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -1909,7 +2011,8 @@ void MainWindow::on_actionExecutionLogs_triggered(bool checked)
m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon("view-calendar-journal")); m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon("view-calendar-journal"));
#endif #endif
} }
else { else
{
delete m_executionLog; delete m_executionLog;
} }
@ -1997,7 +2100,8 @@ QIcon MainWindow::getSystrayIcon() const
const TrayIcon::Style style = Preferences::instance()->trayIconStyle(); const TrayIcon::Style style = Preferences::instance()->trayIconStyle();
// on Linux we use theme icons, and icons from resources everywhere else // on Linux we use theme icons, and icons from resources everywhere else
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
switch (style) { switch (style)
{
case TrayIcon::NORMAL: case TrayIcon::NORMAL:
return QIcon::fromTheme(QLatin1String("qbittorrent-tray")); return QIcon::fromTheme(QLatin1String("qbittorrent-tray"));
case TrayIcon::MONO_DARK: case TrayIcon::MONO_DARK:
@ -2008,7 +2112,8 @@ QIcon MainWindow::getSystrayIcon() const
break; break;
} }
#else #else
switch (style) { switch (style)
{
case TrayIcon::NORMAL: case TrayIcon::NORMAL:
return UIThemeManager::instance()->getIcon(QLatin1String("qbittorrent-tray")); return UIThemeManager::instance()->getIcon(QLatin1String("qbittorrent-tray"));
case TrayIcon::MONO_DARK: case TrayIcon::MONO_DARK:
@ -2057,7 +2162,8 @@ void MainWindow::installPython()
void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result) void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
{ {
if (result.status != Net::DownloadStatus::Success) { if (result.status != Net::DownloadStatus::Success)
{
setCursor(QCursor(Qt::ArrowCursor)); setCursor(QCursor(Qt::ArrowCursor));
QMessageBox::warning( QMessageBox::warning(
this, tr("Download error") this, tr("Download error")
@ -2084,7 +2190,8 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
Utils::Fs::forceRemove(result.filePath + ".exe"); Utils::Fs::forceRemove(result.filePath + ".exe");
// Reload search engine // Reload search engine
if (Utils::ForeignApps::pythonInfo().isSupportedVersion()) { if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
{
m_ui->actionSearchWidget->setChecked(true); m_ui->actionSearchWidget->setChecked(true);
displaySearchTab(true); displaySearchTab(true);
} }

View file

@ -84,7 +84,8 @@ namespace
QString languageToLocalizedString(const QLocale &locale) QString languageToLocalizedString(const QLocale &locale)
{ {
switch (locale.language()) { switch (locale.language())
{
case QLocale::Arabic: return QString::fromUtf8(C_LOCALE_ARABIC); case QLocale::Arabic: return QString::fromUtf8(C_LOCALE_ARABIC);
case QLocale::Armenian: return QString::fromUtf8(C_LOCALE_ARMENIAN); case QLocale::Armenian: return QString::fromUtf8(C_LOCALE_ARMENIAN);
case QLocale::Basque: return QString::fromUtf8(C_LOCALE_BASQUE); case QLocale::Basque: return QString::fromUtf8(C_LOCALE_BASQUE);
@ -92,7 +93,8 @@ namespace
case QLocale::Byelorussian: return QString::fromUtf8(C_LOCALE_BYELORUSSIAN); case QLocale::Byelorussian: return QString::fromUtf8(C_LOCALE_BYELORUSSIAN);
case QLocale::Catalan: return QString::fromUtf8(C_LOCALE_CATALAN); case QLocale::Catalan: return QString::fromUtf8(C_LOCALE_CATALAN);
case QLocale::Chinese: case QLocale::Chinese:
switch (locale.country()) { switch (locale.country())
{
case QLocale::China: return QString::fromUtf8(C_LOCALE_CHINESE_SIMPLIFIED); case QLocale::China: return QString::fromUtf8(C_LOCALE_CHINESE_SIMPLIFIED);
case QLocale::HongKong: return QString::fromUtf8(C_LOCALE_CHINESE_TRADITIONAL_HK); case QLocale::HongKong: return QString::fromUtf8(C_LOCALE_CHINESE_TRADITIONAL_HK);
default: return QString::fromUtf8(C_LOCALE_CHINESE_TRADITIONAL_TW); default: return QString::fromUtf8(C_LOCALE_CHINESE_TRADITIONAL_TW);
@ -102,7 +104,8 @@ namespace
case QLocale::Danish: return QString::fromUtf8(C_LOCALE_DANISH); case QLocale::Danish: return QString::fromUtf8(C_LOCALE_DANISH);
case QLocale::Dutch: return QString::fromUtf8(C_LOCALE_DUTCH); case QLocale::Dutch: return QString::fromUtf8(C_LOCALE_DUTCH);
case QLocale::English: case QLocale::English:
switch (locale.country()) { switch (locale.country())
{
case QLocale::Australia: return QString::fromUtf8(C_LOCALE_ENGLISH_AUSTRALIA); case QLocale::Australia: return QString::fromUtf8(C_LOCALE_ENGLISH_AUSTRALIA);
case QLocale::UnitedKingdom: return QString::fromUtf8(C_LOCALE_ENGLISH_UNITEDKINGDOM); case QLocale::UnitedKingdom: return QString::fromUtf8(C_LOCALE_ENGLISH_UNITEDKINGDOM);
default: return QString::fromUtf8(C_LOCALE_ENGLISH); default: return QString::fromUtf8(C_LOCALE_ENGLISH);
@ -195,7 +198,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
int maxHeight = -1; int maxHeight = -1;
for (int i = 0; i < m_ui->tabSelection->count(); ++i) for (int i = 0; i < m_ui->tabSelection->count(); ++i)
maxHeight = std::max(maxHeight, m_ui->tabSelection->visualItemRect(m_ui->tabSelection->item(i)).size().height()); maxHeight = std::max(maxHeight, m_ui->tabSelection->visualItemRect(m_ui->tabSelection->item(i)).size().height());
for (int i = 0; i < m_ui->tabSelection->count(); ++i) { for (int i = 0; i < m_ui->tabSelection->count(); ++i)
{
const QSize size(std::numeric_limits<int>::max(), static_cast<int>(maxHeight * 1.2)); const QSize size(std::numeric_limits<int>::max(), static_cast<int>(maxHeight * 1.2));
m_ui->tabSelection->item(i)->setSizeHint(size); m_ui->tabSelection->item(i)->setSizeHint(size);
} }
@ -225,8 +229,10 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->hsplitter->setCollapsible(1, false); m_ui->hsplitter->setCollapsible(1, false);
// Get apply button in button box // Get apply button in button box
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons(); const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons();
for (QAbstractButton *button : buttons) { for (QAbstractButton *button : buttons)
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) { {
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
{
m_applyButton = button; m_applyButton = button;
break; break;
} }
@ -263,7 +269,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->checkShowSystray->setVisible(false); m_ui->checkShowSystray->setVisible(false);
#else #else
// Disable systray integration if it is not supported by the system // Disable systray integration if it is not supported by the system
if (!QSystemTrayIcon::isSystemTrayAvailable()) { if (!QSystemTrayIcon::isSystemTrayAvailable())
{
m_ui->checkShowSystray->setChecked(false); m_ui->checkShowSystray->setChecked(false);
m_ui->checkShowSystray->setEnabled(false); m_ui->checkShowSystray->setEnabled(false);
m_ui->labelTrayIconStyle->setVisible(false); m_ui->labelTrayIconStyle->setVisible(false);
@ -567,19 +574,23 @@ void OptionsDialog::initializeLanguageCombo()
// List language files // List language files
const QDir langDir(":/lang"); const QDir langDir(":/lang");
const QStringList langFiles = langDir.entryList(QStringList("qbittorrent_*.qm"), QDir::Files); const QStringList langFiles = langDir.entryList(QStringList("qbittorrent_*.qm"), QDir::Files);
for (const QString &langFile : langFiles) { for (const QString &langFile : langFiles)
{
QString localeStr = langFile.mid(12); // remove "qbittorrent_" QString localeStr = langFile.mid(12); // remove "qbittorrent_"
localeStr.chop(3); // Remove ".qm" localeStr.chop(3); // Remove ".qm"
QString languageName; QString languageName;
if (localeStr.startsWith("eo", Qt::CaseInsensitive)) { if (localeStr.startsWith("eo", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale. Esperanto isn't a "real" language. // QLocale doesn't work with that locale. Esperanto isn't a "real" language.
languageName = QString::fromUtf8(C_LOCALE_ESPERANTO); languageName = QString::fromUtf8(C_LOCALE_ESPERANTO);
} }
else if (localeStr.startsWith("ltg", Qt::CaseInsensitive)) { else if (localeStr.startsWith("ltg", Qt::CaseInsensitive))
{
// QLocale doesn't work with that locale. // QLocale doesn't work with that locale.
languageName = QString::fromUtf8(C_LOCALE_LATGALIAN); languageName = QString::fromUtf8(C_LOCALE_LATGALIAN);
} }
else { else
{
QLocale locale(localeStr); QLocale locale(localeStr);
languageName = languageToLocalizedString(locale); languageName = languageToLocalizedString(locale);
} }
@ -634,7 +645,8 @@ void OptionsDialog::saveWindowState() const
pref->setPrefSize(size()); pref->setPrefSize(size());
// Splitter size // Splitter size
const QStringList sizesStr = { const QStringList sizesStr =
{
QString::number(m_ui->hsplitter->sizes().first()), QString::number(m_ui->hsplitter->sizes().first()),
QString::number(m_ui->hsplitter->sizes().last()) QString::number(m_ui->hsplitter->sizes().last())
}; };
@ -647,7 +659,8 @@ void OptionsDialog::saveOptions()
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
// Load the translation // Load the translation
QString locale = getLocale(); QString locale = getLocale();
if (pref->getLocale() != locale) { if (pref->getLocale() != locale)
{
auto *translator = new QTranslator; auto *translator = new QTranslator;
if (translator->load(QLatin1String(":/lang/qbittorrent_") + locale)) if (translator->load(QLatin1String(":/lang/qbittorrent_") + locale))
qDebug("%s locale recognized, using translation.", qUtf8Printable(locale)); qDebug("%s locale recognized, using translation.", qUtf8Printable(locale));
@ -689,12 +702,14 @@ void OptionsDialog::saveOptions()
Preferences::setMagnetLinkAssoc(m_ui->checkAssociateMagnetLinks->isChecked()); Preferences::setMagnetLinkAssoc(m_ui->checkAssociateMagnetLinks->isChecked());
#endif #endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (m_ui->checkAssociateTorrents->isChecked()) { if (m_ui->checkAssociateTorrents->isChecked())
{
Preferences::setTorrentFileAssoc(); Preferences::setTorrentFileAssoc();
m_ui->checkAssociateTorrents->setChecked(Preferences::isTorrentFileAssocSet()); m_ui->checkAssociateTorrents->setChecked(Preferences::isTorrentFileAssocSet());
m_ui->checkAssociateTorrents->setEnabled(!m_ui->checkAssociateTorrents->isChecked()); m_ui->checkAssociateTorrents->setEnabled(!m_ui->checkAssociateTorrents->isChecked());
} }
if (m_ui->checkAssociateMagnetLinks->isChecked()) { if (m_ui->checkAssociateMagnetLinks->isChecked())
{
Preferences::setMagnetLinkAssoc(); Preferences::setMagnetLinkAssoc();
m_ui->checkAssociateMagnetLinks->setChecked(Preferences::isMagnetLinkAssocSet()); m_ui->checkAssociateMagnetLinks->setChecked(Preferences::isMagnetLinkAssocSet());
m_ui->checkAssociateMagnetLinks->setEnabled(!m_ui->checkAssociateMagnetLinks->isChecked()); m_ui->checkAssociateMagnetLinks->setEnabled(!m_ui->checkAssociateMagnetLinks->isChecked());
@ -810,7 +825,8 @@ void OptionsDialog::saveOptions()
session->setGlobalMaxRatio(getMaxRatio()); session->setGlobalMaxRatio(getMaxRatio());
session->setGlobalMaxSeedingMinutes(getMaxSeedingMinutes()); session->setGlobalMaxSeedingMinutes(getMaxSeedingMinutes());
const QVector<MaxRatioAction> actIndex = { const QVector<MaxRatioAction> actIndex =
{
Pause, Pause,
Remove, Remove,
DeleteFiles, DeleteFiles,
@ -837,7 +853,8 @@ void OptionsDialog::saveOptions()
// End Queueing system preferences // End Queueing system preferences
// Web UI // Web UI
pref->setWebUiEnabled(isWebUiEnabled()); pref->setWebUiEnabled(isWebUiEnabled());
if (isWebUiEnabled()) { if (isWebUiEnabled())
{
pref->setServerDomains(m_ui->textServerDomains->text()); pref->setServerDomains(m_ui->textServerDomains->text());
pref->setWebUiAddress(m_ui->textWebUiAddress->text()); pref->setWebUiAddress(m_ui->textWebUiAddress->text());
pref->setWebUiPort(m_ui->spinWebUiPort->value()); pref->setWebUiPort(m_ui->spinWebUiPort->value());
@ -888,7 +905,8 @@ bool OptionsDialog::isIPFilteringEnabled() const
Net::ProxyType OptionsDialog::getProxyType() const Net::ProxyType OptionsDialog::getProxyType() const
{ {
switch (m_ui->comboProxyType->currentIndex()) { switch (m_ui->comboProxyType->currentIndex())
{
case 1: case 1:
return Net::ProxyType::SOCKS4; return Net::ProxyType::SOCKS4;
case 2: case 2:
@ -927,7 +945,8 @@ void OptionsDialog::loadOptions()
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
m_ui->checkShowSystray->setChecked(pref->systrayIntegration()); m_ui->checkShowSystray->setChecked(pref->systrayIntegration());
if (m_ui->checkShowSystray->isChecked()) { if (m_ui->checkShowSystray->isChecked())
{
m_ui->checkMinimizeToSysTray->setChecked(pref->minimizeToTray()); m_ui->checkMinimizeToSysTray->setChecked(pref->minimizeToTray());
m_ui->checkCloseToSystray->setChecked(pref->closeToTray()); m_ui->checkCloseToSystray->setChecked(pref->closeToTray());
m_ui->comboTrayIcon->setCurrentIndex(pref->trayIconStyle()); m_ui->comboTrayIcon->setCurrentIndex(pref->trayIconStyle());
@ -1001,12 +1020,14 @@ void OptionsDialog::loadOptions()
m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled()); m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled());
strValue = session->torrentExportDirectory(); strValue = session->torrentExportDirectory();
if (strValue.isEmpty()) { if (strValue.isEmpty())
{
// Disable // Disable
m_ui->checkExportDir->setChecked(false); m_ui->checkExportDir->setChecked(false);
m_ui->textExportDir->setEnabled(false); m_ui->textExportDir->setEnabled(false);
} }
else { else
{
// Enable // Enable
m_ui->checkExportDir->setChecked(true); m_ui->checkExportDir->setChecked(true);
m_ui->textExportDir->setEnabled(true); m_ui->textExportDir->setEnabled(true);
@ -1014,12 +1035,14 @@ void OptionsDialog::loadOptions()
} }
strValue = session->finishedTorrentExportDirectory(); strValue = session->finishedTorrentExportDirectory();
if (strValue.isEmpty()) { if (strValue.isEmpty())
{
// Disable // Disable
m_ui->checkExportDirFin->setChecked(false); m_ui->checkExportDirFin->setChecked(false);
m_ui->textExportDirFin->setEnabled(false); m_ui->textExportDirFin->setEnabled(false);
} }
else { else
{
// Enable // Enable
m_ui->checkExportDirFin->setChecked(true); m_ui->checkExportDirFin->setChecked(true);
m_ui->textExportDirFin->setEnabled(true); m_ui->textExportDirFin->setEnabled(true);
@ -1060,49 +1083,57 @@ void OptionsDialog::loadOptions()
m_ui->spinPort->setDisabled(m_ui->checkRandomPort->isChecked()); m_ui->spinPort->setDisabled(m_ui->checkRandomPort->isChecked());
intValue = session->maxConnections(); intValue = session->maxConnections();
if (intValue > 0) { if (intValue > 0)
{
// enable // enable
m_ui->checkMaxConnecs->setChecked(true); m_ui->checkMaxConnecs->setChecked(true);
m_ui->spinMaxConnec->setEnabled(true); m_ui->spinMaxConnec->setEnabled(true);
m_ui->spinMaxConnec->setValue(intValue); m_ui->spinMaxConnec->setValue(intValue);
} }
else { else
{
// disable // disable
m_ui->checkMaxConnecs->setChecked(false); m_ui->checkMaxConnecs->setChecked(false);
m_ui->spinMaxConnec->setEnabled(false); m_ui->spinMaxConnec->setEnabled(false);
} }
intValue = session->maxConnectionsPerTorrent(); intValue = session->maxConnectionsPerTorrent();
if (intValue > 0) { if (intValue > 0)
{
// enable // enable
m_ui->checkMaxConnecsPerTorrent->setChecked(true); m_ui->checkMaxConnecsPerTorrent->setChecked(true);
m_ui->spinMaxConnecPerTorrent->setEnabled(true); m_ui->spinMaxConnecPerTorrent->setEnabled(true);
m_ui->spinMaxConnecPerTorrent->setValue(intValue); m_ui->spinMaxConnecPerTorrent->setValue(intValue);
} }
else { else
{
// disable // disable
m_ui->checkMaxConnecsPerTorrent->setChecked(false); m_ui->checkMaxConnecsPerTorrent->setChecked(false);
m_ui->spinMaxConnecPerTorrent->setEnabled(false); m_ui->spinMaxConnecPerTorrent->setEnabled(false);
} }
intValue = session->maxUploads(); intValue = session->maxUploads();
if (intValue > 0) { if (intValue > 0)
{
// enable // enable
m_ui->checkMaxUploads->setChecked(true); m_ui->checkMaxUploads->setChecked(true);
m_ui->spinMaxUploads->setEnabled(true); m_ui->spinMaxUploads->setEnabled(true);
m_ui->spinMaxUploads->setValue(intValue); m_ui->spinMaxUploads->setValue(intValue);
} }
else { else
{
// disable // disable
m_ui->checkMaxUploads->setChecked(false); m_ui->checkMaxUploads->setChecked(false);
m_ui->spinMaxUploads->setEnabled(false); m_ui->spinMaxUploads->setEnabled(false);
} }
intValue = session->maxUploadsPerTorrent(); intValue = session->maxUploadsPerTorrent();
if (intValue > 0) { if (intValue > 0)
{
// enable // enable
m_ui->checkMaxUploadsPerTorrent->setChecked(true); m_ui->checkMaxUploadsPerTorrent->setChecked(true);
m_ui->spinMaxUploadsPerTorrent->setEnabled(true); m_ui->spinMaxUploadsPerTorrent->setEnabled(true);
m_ui->spinMaxUploadsPerTorrent->setValue(intValue); m_ui->spinMaxUploadsPerTorrent->setValue(intValue);
} }
else { else
{
// disable // disable
m_ui->checkMaxUploadsPerTorrent->setChecked(false); m_ui->checkMaxUploadsPerTorrent->setChecked(false);
m_ui->spinMaxUploadsPerTorrent->setEnabled(false); m_ui->spinMaxUploadsPerTorrent->setEnabled(false);
@ -1112,7 +1143,8 @@ void OptionsDialog::loadOptions()
Net::ProxyConfiguration proxyConf = proxyConfigManager->proxyConfiguration(); Net::ProxyConfiguration proxyConf = proxyConfigManager->proxyConfiguration();
using Net::ProxyType; using Net::ProxyType;
bool useProxyAuth = false; bool useProxyAuth = false;
switch (proxyConf.type) { switch (proxyConf.type)
{
case ProxyType::SOCKS4: case ProxyType::SOCKS4:
m_ui->comboProxyType->setCurrentIndex(1); m_ui->comboProxyType->setCurrentIndex(1);
break; break;
@ -1185,32 +1217,37 @@ void OptionsDialog::loadOptions()
m_ui->spinUploadRateForSlowTorrents->setValue(session->uploadRateForSlowTorrents()); m_ui->spinUploadRateForSlowTorrents->setValue(session->uploadRateForSlowTorrents());
m_ui->spinSlowTorrentsInactivityTimer->setValue(session->slowTorrentsInactivityTimer()); m_ui->spinSlowTorrentsInactivityTimer->setValue(session->slowTorrentsInactivityTimer());
if (session->globalMaxRatio() >= 0.) { if (session->globalMaxRatio() >= 0.)
{
// Enable // Enable
m_ui->checkMaxRatio->setChecked(true); m_ui->checkMaxRatio->setChecked(true);
m_ui->spinMaxRatio->setEnabled(true); m_ui->spinMaxRatio->setEnabled(true);
m_ui->comboRatioLimitAct->setEnabled(true); m_ui->comboRatioLimitAct->setEnabled(true);
m_ui->spinMaxRatio->setValue(session->globalMaxRatio()); m_ui->spinMaxRatio->setValue(session->globalMaxRatio());
} }
else { else
{
// Disable // Disable
m_ui->checkMaxRatio->setChecked(false); m_ui->checkMaxRatio->setChecked(false);
m_ui->spinMaxRatio->setEnabled(false); m_ui->spinMaxRatio->setEnabled(false);
} }
if (session->globalMaxSeedingMinutes() >= 0) { if (session->globalMaxSeedingMinutes() >= 0)
{
// Enable // Enable
m_ui->checkMaxSeedingMinutes->setChecked(true); m_ui->checkMaxSeedingMinutes->setChecked(true);
m_ui->spinMaxSeedingMinutes->setEnabled(true); m_ui->spinMaxSeedingMinutes->setEnabled(true);
m_ui->spinMaxSeedingMinutes->setValue(session->globalMaxSeedingMinutes()); m_ui->spinMaxSeedingMinutes->setValue(session->globalMaxSeedingMinutes());
} }
else { else
{
// Disable // Disable
m_ui->checkMaxSeedingMinutes->setChecked(false); m_ui->checkMaxSeedingMinutes->setChecked(false);
m_ui->spinMaxSeedingMinutes->setEnabled(false); m_ui->spinMaxSeedingMinutes->setEnabled(false);
} }
m_ui->comboRatioLimitAct->setEnabled((session->globalMaxSeedingMinutes() >= 0) || (session->globalMaxRatio() >= 0.)); m_ui->comboRatioLimitAct->setEnabled((session->globalMaxSeedingMinutes() >= 0) || (session->globalMaxRatio() >= 0.));
const QHash<MaxRatioAction, int> actIndex = { const QHash<MaxRatioAction, int> actIndex =
{
{Pause, 0}, {Pause, 0},
{Remove, 1}, {Remove, 1},
{DeleteFiles, 2}, {DeleteFiles, 2},
@ -1386,16 +1423,20 @@ int OptionsDialog::getMaxUploadsPerTorrent() const
void OptionsDialog::on_buttonBox_accepted() void OptionsDialog::on_buttonBox_accepted()
{ {
if (m_applyButton->isEnabled()) { if (m_applyButton->isEnabled())
if (!schedTimesOk()) { {
if (!schedTimesOk())
{
m_ui->tabSelection->setCurrentRow(TAB_SPEED); m_ui->tabSelection->setCurrentRow(TAB_SPEED);
return; return;
} }
if (!webUIAuthenticationOk()) { if (!webUIAuthenticationOk())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return;
} }
if (!isAlternativeWebUIPathValid()) { if (!isAlternativeWebUIPathValid())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return;
} }
@ -1409,16 +1450,20 @@ void OptionsDialog::on_buttonBox_accepted()
void OptionsDialog::applySettings(QAbstractButton *button) void OptionsDialog::applySettings(QAbstractButton *button)
{ {
if (button == m_applyButton) { if (button == m_applyButton)
if (!schedTimesOk()) { {
if (!schedTimesOk())
{
m_ui->tabSelection->setCurrentRow(TAB_SPEED); m_ui->tabSelection->setCurrentRow(TAB_SPEED);
return; return;
} }
if (!webUIAuthenticationOk()) { if (!webUIAuthenticationOk())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return;
} }
if (!isAlternativeWebUIPathValid()) { if (!isAlternativeWebUIPathValid())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return;
} }
@ -1456,24 +1501,28 @@ void OptionsDialog::toggleComboRatioLimitAct()
void OptionsDialog::enableProxy(const int index) void OptionsDialog::enableProxy(const int index)
{ {
if (index >= 1) { // Any proxy type is used if (index >= 1)
{ // Any proxy type is used
//enable //enable
m_ui->lblProxyIP->setEnabled(true); m_ui->lblProxyIP->setEnabled(true);
m_ui->textProxyIP->setEnabled(true); m_ui->textProxyIP->setEnabled(true);
m_ui->lblProxyPort->setEnabled(true); m_ui->lblProxyPort->setEnabled(true);
m_ui->spinProxyPort->setEnabled(true); m_ui->spinProxyPort->setEnabled(true);
m_ui->checkProxyPeerConnecs->setEnabled(true); m_ui->checkProxyPeerConnecs->setEnabled(true);
if (index >= 2) { // SOCKS5 or HTTP if (index >= 2)
{ // SOCKS5 or HTTP
m_ui->checkProxyAuth->setEnabled(true); m_ui->checkProxyAuth->setEnabled(true);
m_ui->isProxyOnlyForTorrents->setEnabled(true); m_ui->isProxyOnlyForTorrents->setEnabled(true);
} }
else { else
{
m_ui->checkProxyAuth->setEnabled(false); m_ui->checkProxyAuth->setEnabled(false);
m_ui->isProxyOnlyForTorrents->setEnabled(false); m_ui->isProxyOnlyForTorrents->setEnabled(false);
m_ui->isProxyOnlyForTorrents->setChecked(true); m_ui->isProxyOnlyForTorrents->setChecked(true);
} }
} }
else { // No proxy else
{ // No proxy
// disable // disable
m_ui->lblProxyIP->setEnabled(false); m_ui->lblProxyIP->setEnabled(false);
m_ui->textProxyIP->setEnabled(false); m_ui->textProxyIP->setEnabled(false);
@ -1550,13 +1599,16 @@ QString OptionsDialog::getLocale() const
void OptionsDialog::setLocale(const QString &localeStr) void OptionsDialog::setLocale(const QString &localeStr)
{ {
QString name; QString name;
if (localeStr.startsWith("eo", Qt::CaseInsensitive)) { if (localeStr.startsWith("eo", Qt::CaseInsensitive))
{
name = "eo"; name = "eo";
} }
else if (localeStr.startsWith("ltg", Qt::CaseInsensitive)) { else if (localeStr.startsWith("ltg", Qt::CaseInsensitive))
{
name = "ltg"; name = "ltg";
} }
else { else
{
QLocale locale(localeStr); QLocale locale(localeStr);
if (locale.language() == QLocale::Uzbek) if (locale.language() == QLocale::Uzbek)
name = "uz@Latn"; name = "uz@Latn";
@ -1565,15 +1617,18 @@ void OptionsDialog::setLocale(const QString &localeStr)
} }
// Attempt to find exact match // Attempt to find exact match
int index = m_ui->comboI18n->findData(name, Qt::UserRole); int index = m_ui->comboI18n->findData(name, Qt::UserRole);
if (index < 0) { if (index < 0)
{
//Attempt to find a language match without a country //Attempt to find a language match without a country
int pos = name.indexOf('_'); int pos = name.indexOf('_');
if (pos > -1) { if (pos > -1)
{
QString lang = name.left(pos); QString lang = name.left(pos);
index = m_ui->comboI18n->findData(lang, Qt::UserRole); index = m_ui->comboI18n->findData(lang, Qt::UserRole);
} }
} }
if (index < 0) { if (index < 0)
{
// Unrecognized, use US English // Unrecognized, use US English
index = m_ui->comboI18n->findData("en", Qt::UserRole); index = m_ui->comboI18n->findData("en", Qt::UserRole);
Q_ASSERT(index >= 0); Q_ASSERT(index >= 0);
@ -1616,10 +1671,12 @@ void OptionsDialog::on_addScanFolderButton_clicked()
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to monitor"), const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to monitor"),
Utils::Fs::toNativePath(Utils::Fs::folderName(pref->getScanDirsLastPath()))); Utils::Fs::toNativePath(Utils::Fs::folderName(pref->getScanDirsLastPath())));
if (!dir.isEmpty()) { if (!dir.isEmpty())
{
const ScanFoldersModel::PathStatus status = ScanFoldersModel::instance()->addPath(dir, ScanFoldersModel::DEFAULT_LOCATION, QString(), false); const ScanFoldersModel::PathStatus status = ScanFoldersModel::instance()->addPath(dir, ScanFoldersModel::DEFAULT_LOCATION, QString(), false);
QString error; QString error;
switch (status) { switch (status)
{
case ScanFoldersModel::AlreadyInList: case ScanFoldersModel::AlreadyInList:
error = tr("Folder is already being monitored:"); error = tr("Folder is already being monitored:");
break; break;
@ -1649,7 +1706,8 @@ void OptionsDialog::on_removeScanFolderButton_clicked()
if (selected.isEmpty()) if (selected.isEmpty())
return; return;
Q_ASSERT(selected.count() == ScanFoldersModel::instance()->columnCount()); Q_ASSERT(selected.count() == ScanFoldersModel::instance()->columnCount());
for (const QModelIndex &index : selected) { for (const QModelIndex &index : selected)
{
if (index.column() == ScanFoldersModel::WATCH) if (index.column() == ScanFoldersModel::WATCH)
m_removedScanDirs << index.data().toString(); m_removedScanDirs << index.data().toString();
} }
@ -1704,13 +1762,15 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s
return; return;
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly))
{
if (showError == ShowError::Show) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid path"), file.errorString()); QMessageBox::warning(this, tr("Invalid path"), file.errorString());
return; return;
} }
if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) { if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE)))
{
if (showError == ShowError::Show) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate.")); QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate."));
return; return;
@ -1728,13 +1788,15 @@ void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError sh
return; return;
QFile file(path); QFile file(path);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly))
{
if (showError == ShowError::Show) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid path"), file.errorString()); QMessageBox::warning(this, tr("Invalid path"), file.errorString());
return; return;
} }
if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) { if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE)))
{
if (showError == ShowError::Show) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key.")); QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key."));
return; return;
@ -1779,7 +1841,8 @@ void OptionsDialog::handleIPFilterParsed(bool error, int ruleCount)
bool OptionsDialog::schedTimesOk() bool OptionsDialog::schedTimesOk()
{ {
if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time()) { if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time())
{
QMessageBox::warning(this, tr("Time Error"), tr("The start time and the end time can't be the same.")); QMessageBox::warning(this, tr("Time Error"), tr("The start time and the end time can't be the same."));
return false; return false;
} }
@ -1788,11 +1851,13 @@ bool OptionsDialog::schedTimesOk()
bool OptionsDialog::webUIAuthenticationOk() bool OptionsDialog::webUIAuthenticationOk()
{ {
if (webUiUsername().length() < 3) { if (webUiUsername().length() < 3)
{
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI username must be at least 3 characters long.")); QMessageBox::warning(this, tr("Length Error"), tr("The Web UI username must be at least 3 characters long."));
return false; return false;
} }
if (!webUiPassword().isEmpty() && (webUiPassword().length() < 6)) { if (!webUiPassword().isEmpty() && (webUiPassword().length() < 6))
{
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI password must be at least 6 characters long.")); QMessageBox::warning(this, tr("Length Error"), tr("The Web UI password must be at least 6 characters long."));
return false; return false;
} }
@ -1801,7 +1866,8 @@ bool OptionsDialog::webUIAuthenticationOk()
bool OptionsDialog::isAlternativeWebUIPathValid() bool OptionsDialog::isAlternativeWebUIPathValid()
{ {
if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().trimmed().isEmpty()) { if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().trimmed().isEmpty())
{
QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank.")); QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank."));
return false; return false;
} }

View file

@ -54,12 +54,14 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option); QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
drawBackground(painter, opt, index); drawBackground(painter, opt, index);
switch (index.column()) { switch (index.column())
{
case PreviewSelectDialog::SIZE: case PreviewSelectDialog::SIZE:
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong())); QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
break; break;
case PreviewSelectDialog::PROGRESS: { case PreviewSelectDialog::PROGRESS:
{
const qreal progress = (index.data().toReal() * 100); const qreal progress = (index.data().toReal() * 100);
QStyleOptionProgressBar newopt; QStyleOptionProgressBar newopt;

View file

@ -86,12 +86,14 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
// Fill list in // Fill list in
const QVector<qreal> fp = torrent->filesProgress(); const QVector<qreal> fp = torrent->filesProgress();
int nbFiles = torrent->filesCount(); int nbFiles = torrent->filesCount();
for (int i = 0; i < nbFiles; ++i) { for (int i = 0; i < nbFiles; ++i)
{
QString fileName = torrent->fileName(i); QString fileName = torrent->fileName(i);
if (fileName.endsWith(QB_EXT)) if (fileName.endsWith(QB_EXT))
fileName.chop(4); fileName.chop(4);
QString extension = Utils::Fs::fileExtension(fileName).toUpper(); QString extension = Utils::Fs::fileExtension(fileName).toUpper();
if (Utils::Misc::isPreviewable(extension)) { if (Utils::Misc::isPreviewable(extension))
{
int row = m_previewListModel->rowCount(); int row = m_previewListModel->rowCount();
m_previewListModel->insertRow(row); m_previewListModel->insertRow(row);
m_previewListModel->setData(m_previewListModel->index(row, NAME), fileName); m_previewListModel->setData(m_previewListModel->index(row, NAME), fileName);
@ -128,7 +130,8 @@ void PreviewSelectDialog::previewButtonClicked()
// Only one file should be selected // Only one file should be selected
const QString path = absolutePaths.at(selectedIndexes.at(0).data().toInt()); const QString path = absolutePaths.at(selectedIndexes.at(0).data().toInt());
// File // File
if (!QFile::exists(path)) { if (!QFile::exists(path))
{
const bool isSingleFile = (m_previewListModel->rowCount() == 1); const bool isSingleFile = (m_previewListModel->rowCount() == 1);
QWidget *parent = isSingleFile ? this->parentWidget() : this; QWidget *parent = isSingleFile ? this->parentWidget() : this;
QMessageBox::critical(parent, tr("Preview impossible") QMessageBox::critical(parent, tr("Preview impossible")
@ -156,7 +159,8 @@ void PreviewSelectDialog::loadWindowState()
Utils::Gui::resize(this, m_storeDialogSize); Utils::Gui::resize(this, m_storeDialogSize);
// Restore TreeView Header state // Restore TreeView Header state
if (!m_storeTreeHeaderState.value().isEmpty()) { if (!m_storeTreeHeaderState.value().isEmpty())
{
m_headerStateInitialized = m_ui->previewList->header()->restoreState(m_storeTreeHeaderState); m_headerStateInitialized = m_ui->previewList->header()->restoreState(m_storeTreeHeaderState);
} }
} }
@ -164,14 +168,16 @@ void PreviewSelectDialog::loadWindowState()
void PreviewSelectDialog::showEvent(QShowEvent *event) void PreviewSelectDialog::showEvent(QShowEvent *event)
{ {
// event originated from system // event originated from system
if (event->spontaneous()) { if (event->spontaneous())
{
QDialog::showEvent(event); QDialog::showEvent(event);
return; return;
} }
// Default size, have to be called after show(), because width is needed // Default size, have to be called after show(), because width is needed
// Set Name column width to 60% of TreeView // Set Name column width to 60% of TreeView
if (!m_headerStateInitialized) { if (!m_headerStateInitialized)
{
const int nameSize = (m_ui->previewList->size().width() * 0.6); const int nameSize = (m_ui->previewList->size().width() * 0.6);
m_ui->previewList->header()->resizeSection(0, nameSize); m_ui->previewList->header()->resizeSection(0, nameSize);
m_headerStateInitialized = true; m_headerStateInitialized = true;

View file

@ -70,7 +70,8 @@ void ProgramUpdater::checkForUpdates()
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{ {
if (result.status != Net::DownloadStatus::Success) { if (result.status != Net::DownloadStatus::Success)
{
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString; qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished(false, QString(), m_invokedByUser); emit updateCheckFinished(false, QString(), m_invokedByUser);
return; return;
@ -81,7 +82,8 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
const QString OS_TYPE {"Mac OS X"}; const QString OS_TYPE {"Mac OS X"};
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
const QString OS_TYPE {(::IsWindows7OrGreater() const QString OS_TYPE
{(::IsWindows7OrGreater()
&& QSysInfo::currentCpuArchitecture().endsWith("64")) && QSysInfo::currentCpuArchitecture().endsWith("64"))
? "Windows x64" : "Windows"}; ? "Windows x64" : "Windows"};
#endif #endif
@ -92,10 +94,12 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
QString updateLink; QString updateLink;
QString type; QString type;
while (!xml.atEnd()) { while (!xml.atEnd())
{
xml.readNext(); xml.readNext();
if (xml.isStartElement()) { if (xml.isStartElement())
{
if (xml.name() == "item") if (xml.name() == "item")
inItem = true; inItem = true;
else if (inItem && xml.name() == "link") else if (inItem && xml.name() == "link")
@ -105,11 +109,15 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
else if (inItem && xml.name() == "version") else if (inItem && xml.name() == "version")
version = getStringValue(xml); version = getStringValue(xml);
} }
else if (xml.isEndElement()) { else if (xml.isEndElement())
if (inItem && xml.name() == "item") { {
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0) { if (inItem && xml.name() == "item")
{
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0)
{
qDebug("The last update available is %s", qUtf8Printable(version)); qDebug("The last update available is %s", qUtf8Printable(version));
if (!version.isEmpty()) { if (!version.isEmpty())
{
qDebug("Detected version is %s", qUtf8Printable(version)); qDebug("Detected version is %s", qUtf8Printable(version));
if (isVersionMoreRecent(version)) if (isVersionMoreRecent(version))
m_updateUrl = updateLink; m_updateUrl = updateLink;
@ -138,12 +146,14 @@ void ProgramUpdater::updateProgram()
bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const
{ {
const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION); const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION);
if (regVerMatch.hasMatch()) { if (regVerMatch.hasMatch())
{
const QString localVersion = regVerMatch.captured(1); const QString localVersion = regVerMatch.captured(1);
const QVector<QStringRef> remoteParts = remoteVersion.splitRef('.'); const QVector<QStringRef> remoteParts = remoteVersion.splitRef('.');
const QVector<QStringRef> localParts = localVersion.splitRef('.'); const QVector<QStringRef> localParts = localVersion.splitRef('.');
for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i) { for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i)
{
if (remoteParts[i].toInt() > localParts[i].toInt()) if (remoteParts[i].toInt() > localParts[i].toInt())
return true; return true;
if (remoteParts[i].toInt() < localParts[i].toInt()) if (remoteParts[i].toInt() < localParts[i].toInt())

View file

@ -59,7 +59,8 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
// image.x(0) = pieces.x(0.0 >= x < 1.7) // image.x(0) = pieces.x(0.0 >= x < 1.7)
// image.x(1) = pieces.x(1.7 >= x < 3.4) // image.x(1) = pieces.x(1.7 >= x < 3.4)
for (int x = 0; x < reqSize; ++x) { for (int x = 0; x < reqSize; ++x)
{
// R - real // R - real
const float fromR = x * ratio; const float fromR = x * ratio;
const float toR = (x + 1) * ratio; const float toR = (x + 1) * ratio;
@ -80,15 +81,18 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
float value = 0; float value = 0;
// case when calculated range is (15.2 >= x < 15.7) // case when calculated range is (15.2 >= x < 15.7)
if (x2 == toCMinusOne) { if (x2 == toCMinusOne)
{
if (vecin[x2]) if (vecin[x2])
value += ratio; value += ratio;
++x2; ++x2;
} }
// case when (15.2 >= x < 17.8) // case when (15.2 >= x < 17.8)
else { else
{
// subcase (15.2 >= x < 16) // subcase (15.2 >= x < 16)
if (x2 != fromR) { if (x2 != fromR)
{
if (vecin[x2]) if (vecin[x2])
value += 1.0 - (fromR - fromC); value += 1.0 - (fromR - fromC);
++x2; ++x2;
@ -100,7 +104,8 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
value += 1.0; value += 1.0;
// subcase (17 >= x < 17.8) // subcase (17 >= x < 17.8)
if (x2 == toCMinusOne) { if (x2 == toCMinusOne)
{
if (vecin[x2]) if (vecin[x2])
value += 1.0 - (toC - toR); value += 1.0 - (toC - toR);
++x2; ++x2;
@ -123,12 +128,14 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
{ {
// qDebug() << "updateImage"; // qDebug() << "updateImage";
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888); QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
if (image2.isNull()) { if (image2.isNull())
{
qDebug() << "QImage image2() allocation failed, width():" << width(); qDebug() << "QImage image2() allocation failed, width():" << width();
return false; return false;
} }
if (m_pieces.isEmpty()) { if (m_pieces.isEmpty())
{
image2.fill(backgroundColor()); image2.fill(backgroundColor());
image = image2; image = image2;
return true; return true;
@ -138,10 +145,12 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width()); QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
// filling image // filling image
for (int x = 0; x < scaledPieces.size(); ++x) { for (int x = 0; x < scaledPieces.size(); ++x)
{
float piecesToValue = scaledPieces.at(x); float piecesToValue = scaledPieces.at(x);
float piecesToValueDl = scaledPiecesDl.at(x); float piecesToValueDl = scaledPiecesDl.at(x);
if (piecesToValueDl != 0) { if (piecesToValueDl != 0)
{
float fillRatio = piecesToValue + piecesToValueDl; float fillRatio = piecesToValue + piecesToValueDl;
float ratio = piecesToValueDl / fillRatio; float ratio = piecesToValueDl / fillRatio;
@ -150,7 +159,8 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
image2.setPixel(x, 0, mixedColor); image2.setPixel(x, 0, mixedColor);
} }
else { else
{
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]); image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
} }
} }

View file

@ -39,9 +39,11 @@ PeerListSortModel::PeerListSortModel(QObject *parent)
bool PeerListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool PeerListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{ {
switch (sortColumn()) { switch (sortColumn())
{
case PeerListWidget::IP: case PeerListWidget::IP:
case PeerListWidget::CLIENT: { case PeerListWidget::CLIENT:
{
const QString strL = left.data(UnderlyingDataRole).toString(); const QString strL = left.data(UnderlyingDataRole).toString();
const QString strR = right.data(UnderlyingDataRole).toString(); const QString strR = right.data(UnderlyingDataRole).toString();
const int result = Utils::String::naturalCompare(strL, strR, Qt::CaseInsensitive); const int result = Utils::String::naturalCompare(strL, strR, Qt::CaseInsensitive);

View file

@ -126,8 +126,10 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
hideColumn(PeerListColumns::COUNTRY); hideColumn(PeerListColumns::COUNTRY);
// Ensure that at least one column is visible at all times // Ensure that at least one column is visible at all times
bool atLeastOne = false; bool atLeastOne = false;
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) { for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
if (!isColumnHidden(i)) { {
if (!isColumnHidden(i))
{
atLeastOne = true; atLeastOne = true;
break; break;
} }
@ -137,7 +139,8 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
// To also mitigate the above issue, we have to resize each column when // To also mitigate the above issue, we have to resize each column when
// its size is 0, because explicitly 'showing' the column isn't enough // its size is 0, because explicitly 'showing' the column isn't enough
// in the above scenario. // in the above scenario.
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) { for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
{
if ((columnWidth(i) <= 0) && !isColumnHidden(i)) if ((columnWidth(i) <= 0) && !isColumnHidden(i))
resizeColumnToContents(i); resizeColumnToContents(i);
} }
@ -178,7 +181,8 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
menu->setTitle(tr("Column visibility")); menu->setTitle(tr("Column visibility"));
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) { for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
{
if ((i == PeerListColumns::COUNTRY) && !Preferences::instance()->resolvePeerCountries()) if ((i == PeerListColumns::COUNTRY) && !Preferences::instance()->resolvePeerCountries())
continue; continue;
@ -191,7 +195,8 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
connect(menu, &QMenu::triggered, this, [this](const QAction *action) connect(menu, &QMenu::triggered, this, [this](const QAction *action)
{ {
int visibleCols = 0; int visibleCols = 0;
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) { for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
{
if (!isColumnHidden(i)) if (!isColumnHidden(i))
++visibleCols; ++visibleCols;
@ -217,14 +222,17 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
void PeerListWidget::updatePeerHostNameResolutionState() void PeerListWidget::updatePeerHostNameResolutionState()
{ {
if (Preferences::instance()->resolvePeerHostNames()) { if (Preferences::instance()->resolvePeerHostNames())
if (!m_resolver) { {
if (!m_resolver)
{
m_resolver = new Net::ReverseResolution(this); m_resolver = new Net::ReverseResolution(this);
connect(m_resolver, &Net::ReverseResolution::ipResolved, this, &PeerListWidget::handleResolved); connect(m_resolver, &Net::ReverseResolution::ipResolved, this, &PeerListWidget::handleResolved);
loadPeers(m_properties->getCurrentTorrent()); loadPeers(m_properties->getCurrentTorrent());
} }
} }
else { else
{
delete m_resolver; delete m_resolver;
m_resolver = nullptr; m_resolver = nullptr;
} }
@ -237,13 +245,15 @@ void PeerListWidget::updatePeerCountryResolutionState()
return; return;
m_resolveCountries = resolveCountries; m_resolveCountries = resolveCountries;
if (m_resolveCountries) { if (m_resolveCountries)
{
loadPeers(m_properties->getCurrentTorrent()); loadPeers(m_properties->getCurrentTorrent());
showColumn(PeerListColumns::COUNTRY); showColumn(PeerListColumns::COUNTRY);
if (columnWidth(PeerListColumns::COUNTRY) <= 0) if (columnWidth(PeerListColumns::COUNTRY) <= 0)
resizeColumnToContents(PeerListColumns::COUNTRY); resizeColumnToContents(PeerListColumns::COUNTRY);
} }
else { else
{
hideColumn(PeerListColumns::COUNTRY); hideColumn(PeerListColumns::COUNTRY);
} }
} }
@ -258,7 +268,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
// Add Peer Action // Add Peer Action
// Do not allow user to add peers in a private torrent // Do not allow user to add peers in a private torrent
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate()) { if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
{
const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")); const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
connect(addPeerAct, &QAction::triggered, this, [this, torrent]() connect(addPeerAct, &QAction::triggered, this, [this, torrent]()
{ {
@ -274,7 +285,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
}); });
} }
if (!selectionModel()->selectedRows().isEmpty()) { if (!selectionModel()->selectedRows().isEmpty())
{
const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")); const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port"));
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers); connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers);
@ -298,7 +310,8 @@ void PeerListWidget::banSelectedPeers()
QVector<QString> selectedIPs; QVector<QString> selectedIPs;
selectedIPs.reserve(selectedIndexes.size()); selectedIPs.reserve(selectedIndexes.size());
for (const QModelIndex &index : selectedIndexes) { for (const QModelIndex &index : selectedIndexes)
{
const int row = m_proxyModel->mapToSource(index).row(); const int row = m_proxyModel->mapToSource(index).row();
const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text(); const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text();
selectedIPs += ip; selectedIPs += ip;
@ -309,7 +322,8 @@ void PeerListWidget::banSelectedPeers()
, tr("Are you sure you want to permanently ban the selected peers?")); , tr("Are you sure you want to permanently ban the selected peers?"));
if (btn != QMessageBox::Yes) return; if (btn != QMessageBox::Yes) return;
for (const QString &ip : selectedIPs) { for (const QString &ip : selectedIPs)
{
BitTorrent::Session::instance()->banIP(ip); BitTorrent::Session::instance()->banIP(ip);
LogMsg(tr("Peer \"%1\" is manually banned").arg(ip)); LogMsg(tr("Peer \"%1\" is manually banned").arg(ip));
} }
@ -322,7 +336,8 @@ void PeerListWidget::copySelectedPeers()
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(); const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
QStringList selectedPeers; QStringList selectedPeers;
for (const QModelIndex &index : selectedIndexes) { for (const QModelIndex &index : selectedIndexes)
{
const int row = m_proxyModel->mapToSource(index).row(); const int row = m_proxyModel->mapToSource(index).row();
const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text(); const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text();
const QString port = m_listModel->item(row, PeerListColumns::PORT)->text(); const QString port = m_listModel->item(row, PeerListColumns::PORT)->text();
@ -364,19 +379,22 @@ void PeerListWidget::loadPeers(const BitTorrent::TorrentHandle *torrent)
for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i) for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i)
existingPeers << i.key(); existingPeers << i.key();
for (const BitTorrent::PeerInfo &peer : peers) { for (const BitTorrent::PeerInfo &peer : peers)
{
if (peer.address().ip.isNull()) continue; if (peer.address().ip.isNull()) continue;
bool isNewPeer = false; bool isNewPeer = false;
updatePeer(torrent, peer, isNewPeer); updatePeer(torrent, peer, isNewPeer);
if (!isNewPeer) { if (!isNewPeer)
{
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()}; const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
existingPeers.remove(peerEndpoint); existingPeers.remove(peerEndpoint);
} }
} }
// Remove peers that are gone // Remove peers that are gone
for (const PeerEndpoint &peerEndpoint : asConst(existingPeers)) { for (const PeerEndpoint &peerEndpoint : asConst(existingPeers))
{
QStandardItem *item = m_peerItems.take(peerEndpoint); QStandardItem *item = m_peerItems.take(peerEndpoint);
QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip]; QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
@ -411,7 +429,8 @@ void PeerListWidget::updatePeer(const BitTorrent::TorrentHandle *torrent, const
auto itemIter = m_peerItems.find(peerEndpoint); auto itemIter = m_peerItems.find(peerEndpoint);
isNewPeer = (itemIter == m_peerItems.end()); isNewPeer = (itemIter == m_peerItems.end());
if (isNewPeer) { if (isNewPeer)
{
// new item // new item
const int row = m_listModel->rowCount(); const int row = m_listModel->rowCount();
m_listModel->insertRow(row); m_listModel->insertRow(row);
@ -449,9 +468,11 @@ void PeerListWidget::updatePeer(const BitTorrent::TorrentHandle *torrent, const
if (m_resolver) if (m_resolver)
m_resolver->resolve(peerEndpoint.address.ip); m_resolver->resolve(peerEndpoint.address.ip);
if (m_resolveCountries) { if (m_resolveCountries)
{
const QIcon icon = UIThemeManager::instance()->getFlagIcon(peer.country()); const QIcon icon = UIThemeManager::instance()->getFlagIcon(peer.country());
if (!icon.isNull()) { if (!icon.isNull())
{
m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), icon, Qt::DecorationRole); m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), icon, Qt::DecorationRole);
const QString countryName = Net::GeoIPManager::CountryName(peer.country()); const QString countryName = Net::GeoIPManager::CountryName(peer.country());
m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), countryName, Qt::ToolTipRole); m_listModel->setData(m_listModel->index(row, PeerListColumns::COUNTRY), countryName, Qt::ToolTipRole);
@ -479,7 +500,8 @@ void PeerListWidget::handleSortColumnChanged(const int col)
void PeerListWidget::wheelEvent(QWheelEvent *event) void PeerListWidget::wheelEvent(QWheelEvent *event)
{ {
if (event->modifiers() & Qt::ShiftModifier) { if (event->modifiers() & Qt::ShiftModifier)
{
// Shift + scroll = horizontal scroll // Shift + scroll = horizontal scroll
event->accept(); event->accept();

View file

@ -57,18 +57,22 @@ QVector<BitTorrent::PeerAddress> PeersAdditionDialog::askForPeers(QWidget *paren
void PeersAdditionDialog::validateInput() void PeersAdditionDialog::validateInput()
{ {
if (m_ui->textEditPeers->toPlainText().trimmed().isEmpty()) { if (m_ui->textEditPeers->toPlainText().trimmed().isEmpty())
{
QMessageBox::warning(this, tr("No peer entered"), QMessageBox::warning(this, tr("No peer entered"),
tr("Please type at least one peer."), tr("Please type at least one peer."),
QMessageBox::Ok); QMessageBox::Ok);
return; return;
} }
for (const QString &peer : asConst(m_ui->textEditPeers->toPlainText().trimmed().split('\n'))) { for (const QString &peer : asConst(m_ui->textEditPeers->toPlainText().trimmed().split('\n')))
{
const BitTorrent::PeerAddress addr = BitTorrent::PeerAddress::parse(peer); const BitTorrent::PeerAddress addr = BitTorrent::PeerAddress::parse(peer);
if (!addr.ip.isNull()) { if (!addr.ip.isNull())
{
m_peersList.append(addr); m_peersList.append(addr);
} }
else { else
{
QMessageBox::warning(this, tr("Invalid peer"), QMessageBox::warning(this, tr("Invalid peer"),
tr("The peer '%1' is invalid.").arg(peer), tr("The peer '%1' is invalid.").arg(peer),
QMessageBox::Ok); QMessageBox::Ok);

View file

@ -58,7 +58,8 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
// image.x(0) = pieces.x(0.0 >= x < 1.7) // image.x(0) = pieces.x(0.0 >= x < 1.7)
// image.x(1) = pieces.x(1.7 >= x < 3.4) // image.x(1) = pieces.x(1.7 >= x < 3.4)
for (int x = 0; x < reqSize; ++x) { for (int x = 0; x < reqSize; ++x)
{
// R - real // R - real
const float fromR = x * ratio; const float fromR = x * ratio;
const float toR = (x + 1) * ratio; const float toR = (x + 1) * ratio;
@ -79,15 +80,18 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
float value = 0; float value = 0;
// case when calculated range is (15.2 >= x < 15.7) // case when calculated range is (15.2 >= x < 15.7)
if (x2 == toCMinusOne) { if (x2 == toCMinusOne)
{
if (vecin[x2]) if (vecin[x2])
value += ratio * vecin[x2]; value += ratio * vecin[x2];
++x2; ++x2;
} }
// case when (15.2 >= x < 17.8) // case when (15.2 >= x < 17.8)
else { else
{
// subcase (15.2 >= x < 16) // subcase (15.2 >= x < 16)
if (x2 != fromR) { if (x2 != fromR)
{
if (vecin[x2]) if (vecin[x2])
value += (1.0 - (fromR - fromC)) * vecin[x2]; value += (1.0 - (fromR - fromC)) * vecin[x2];
++x2; ++x2;
@ -99,7 +103,8 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
value += vecin[x2]; value += vecin[x2];
// subcase (17 >= x < 17.8) // subcase (17 >= x < 17.8)
if (x2 == toCMinusOne) { if (x2 == toCMinusOne)
{
if (vecin[x2]) if (vecin[x2])
value += (1.0 - (toC - toR)) * vecin[x2]; value += (1.0 - (toC - toR)) * vecin[x2];
++x2; ++x2;
@ -121,12 +126,14 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
bool PieceAvailabilityBar::updateImage(QImage &image) bool PieceAvailabilityBar::updateImage(QImage &image)
{ {
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888); QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
if (image2.isNull()) { if (image2.isNull())
{
qDebug() << "QImage image2() allocation failed, width():" << width(); qDebug() << "QImage image2() allocation failed, width():" << width();
return false; return false;
} }
if (m_pieces.empty()) { if (m_pieces.empty())
{
image2.fill(backgroundColor()); image2.fill(backgroundColor());
image = image2; image = image2;
return true; return true;
@ -135,7 +142,8 @@ bool PieceAvailabilityBar::updateImage(QImage &image)
QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width()); QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width());
// filling image // filling image
for (int x = 0; x < scaledPieces.size(); ++x) { for (int x = 0; x < scaledPieces.size(); ++x)
{
float piecesToValue = scaledPieces.at(x); float piecesToValue = scaledPieces.at(x);
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]); image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
} }

View file

@ -53,7 +53,8 @@ namespace
{ {
public: public:
PieceIndexToImagePos(const BitTorrent::TorrentInfo &torrentInfo, const QImage &image) PieceIndexToImagePos(const BitTorrent::TorrentInfo &torrentInfo, const QImage &image)
: m_bytesPerPixel {((image.width() > 0) && (torrentInfo.totalSize() >= image.width())) : m_bytesPerPixel
{((image.width() > 0) && (torrentInfo.totalSize() >= image.width()))
? torrentInfo.totalSize() / image.width() : -1} ? torrentInfo.totalSize() / image.width() : -1}
, m_torrentInfo {torrentInfo} , m_torrentInfo {torrentInfo}
{ {
@ -133,7 +134,8 @@ void PiecesBar::clear()
bool PiecesBar::event(QEvent *e) bool PiecesBar::event(QEvent *e)
{ {
if (e->type() == QEvent::ToolTip) { if (e->type() == QEvent::ToolTip)
{
showToolTip(static_cast<QHelpEvent *>(e)); showToolTip(static_cast<QHelpEvent *>(e));
return true; return true;
} }
@ -167,17 +169,20 @@ void PiecesBar::paintEvent(QPaintEvent *)
{ {
QPainter painter(this); QPainter painter(this);
QRect imageRect(borderWidth, borderWidth, width() - 2 * borderWidth, height() - 2 * borderWidth); QRect imageRect(borderWidth, borderWidth, width() - 2 * borderWidth, height() - 2 * borderWidth);
if (m_image.isNull()) { if (m_image.isNull())
{
painter.setBrush(backgroundColor()); painter.setBrush(backgroundColor());
painter.drawRect(imageRect); painter.drawRect(imageRect);
} }
else { else
{
if (m_image.width() != imageRect.width()) if (m_image.width() != imageRect.width())
updateImage(m_image); updateImage(m_image);
painter.drawImage(imageRect, m_image); painter.drawImage(imageRect, m_image);
} }
if (!m_highlitedRegion.isNull()) { if (!m_highlitedRegion.isNull())
{
QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)}; QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)};
highlightColor.setAlphaF(0.35); highlightColor.setAlphaF(0.35);
QRect targetHighlightRect {m_highlitedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)}; QRect targetHighlightRect {m_highlitedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
@ -247,19 +252,23 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
QString toolTipText; QString toolTipText;
QTextStream stream(&toolTipText, QIODevice::WriteOnly); QTextStream stream(&toolTipText, QIODevice::WriteOnly);
const bool showDetailedInformation = QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier); const bool showDetailedInformation = QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
if (showDetailedInformation && m_torrent->hasMetadata()) { if (showDetailedInformation && m_torrent->hasMetadata())
{
const int imagePos = e->pos().x() - borderWidth; const int imagePos = e->pos().x() - borderWidth;
if ((imagePos >=0) && (imagePos < m_image.width())) { if ((imagePos >=0) && (imagePos < m_image.width()))
{
stream << "<html><body>"; stream << "<html><body>";
PieceIndexToImagePos transform {m_torrent->info(), m_image}; PieceIndexToImagePos transform {m_torrent->info(), m_image};
int pieceIndex = transform.pieceIndex(imagePos); int pieceIndex = transform.pieceIndex(imagePos);
const QVector<int> files {m_torrent->info().fileIndicesForPiece(pieceIndex)}; const QVector<int> files {m_torrent->info().fileIndicesForPiece(pieceIndex)};
QString tooltipTitle; QString tooltipTitle;
if (files.count() > 1) { if (files.count() > 1)
{
tooltipTitle = tr("Files in this piece:"); tooltipTitle = tr("Files in this piece:");
} }
else { else
{
if (m_torrent->info().fileSize(files.front()) == m_torrent->info().pieceLength(pieceIndex)) if (m_torrent->info().fileSize(files.front()) == m_torrent->info().pieceLength(pieceIndex))
tooltipTitle = tr("File in this piece"); tooltipTitle = tr("File in this piece");
else else
@ -268,14 +277,16 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
DetailedTooltipRenderer renderer(stream, tooltipTitle); DetailedTooltipRenderer renderer(stream, tooltipTitle);
for (int f : files) { for (int f : files)
{
const QString filePath {m_torrent->info().filePath(f)}; const QString filePath {m_torrent->info().filePath(f)};
renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath); renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath);
} }
stream << "</body></html>"; stream << "</body></html>";
} }
} }
else { else
{
stream << simpleToolTipText(); stream << simpleToolTipText();
if (showDetailedInformation) // metadata are not available at this point if (showDetailedInformation) // metadata are not available at this point
stream << '\n' << tr("Wait until metadata become available to see detailed information"); stream << '\n' << tr("Wait until metadata become available to see detailed information");
@ -297,17 +308,20 @@ void PiecesBar::highlightFile(int imagePos)
int pieceIndex = transform.pieceIndex(imagePos); int pieceIndex = transform.pieceIndex(imagePos);
QVector<int> fileIndices {m_torrent->info().fileIndicesForPiece(pieceIndex)}; QVector<int> fileIndices {m_torrent->info().fileIndicesForPiece(pieceIndex)};
if (fileIndices.count() == 1) { if (fileIndices.count() == 1)
{
BitTorrent::TorrentInfo::PieceRange filePieces = m_torrent->info().filePieces(fileIndices.first()); BitTorrent::TorrentInfo::PieceRange filePieces = m_torrent->info().filePieces(fileIndices.first());
ImageRange imageRange = transform.imagePos(filePieces); ImageRange imageRange = transform.imagePos(filePieces);
QRect newHighlitedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()}; QRect newHighlitedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()};
if (newHighlitedRegion != m_highlitedRegion) { if (newHighlitedRegion != m_highlitedRegion)
{
m_highlitedRegion = newHighlitedRegion; m_highlitedRegion = newHighlitedRegion;
update(); update();
} }
} }
else if (!m_highlitedRegion.isEmpty()) { else if (!m_highlitedRegion.isEmpty())
{
m_highlitedRegion = QRect(); m_highlitedRegion = QRect();
update(); update();
} }
@ -316,7 +330,8 @@ void PiecesBar::highlightFile(int imagePos)
void PiecesBar::updatePieceColors() void PiecesBar::updatePieceColors()
{ {
m_pieceColors = QVector<QRgb>(256); m_pieceColors = QVector<QRgb>(256);
for (int i = 0; i < 256; ++i) { for (int i = 0; i < 256; ++i)
{
float ratio = (i / 255.0); float ratio = (i / 255.0);
m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio); m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio);
} }

View file

@ -194,7 +194,8 @@ void PropertiesWidget::showPiecesDownloaded(bool show)
void PropertiesWidget::setVisibility(const bool visible) void PropertiesWidget::setVisibility(const bool visible)
{ {
if (!visible && (m_state == VISIBLE)) { if (!visible && (m_state == VISIBLE))
{
const int tabBarHeight = m_tabBar->geometry().height(); // take height before hiding const int tabBarHeight = m_tabBar->geometry().height(); // take height before hiding
auto *hSplitter = static_cast<QSplitter *>(parentWidget()); auto *hSplitter = static_cast<QSplitter *>(parentWidget());
m_ui->stackedProperties->setVisible(false); m_ui->stackedProperties->setVisible(false);
@ -210,7 +211,8 @@ void PropertiesWidget::setVisibility(const bool visible)
return; return;
} }
if (visible && (m_state == REDUCED)) { if (visible && (m_state == REDUCED))
{
m_ui->stackedProperties->setVisible(true); m_ui->stackedProperties->setVisible(true);
auto *hSplitter = static_cast<QSplitter *>(parentWidget()); auto *hSplitter = static_cast<QSplitter *>(parentWidget());
if (m_handleWidth != -1) if (m_handleWidth != -1)
@ -314,7 +316,8 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent
// Hash // Hash
m_ui->labelHashVal->setText(m_torrent->hash()); m_ui->labelHashVal->setText(m_torrent->hash());
m_propListModel->model()->clear(); m_propListModel->model()->clear();
if (m_torrent->hasMetadata()) { if (m_torrent->hasMetadata())
{
// Creation date // Creation date
m_ui->labelCreatedOnVal->setText(m_torrent->creationDate().toString(Qt::DefaultLocaleShortDate)); m_ui->labelCreatedOnVal->setText(m_torrent->creationDate().toString(Qt::DefaultLocaleShortDate));
@ -333,7 +336,8 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent
// Expand single-item folders recursively // Expand single-item folders recursively
QModelIndex currentIndex; QModelIndex currentIndex;
while (m_propListModel->rowCount(currentIndex) == 1) { while (m_propListModel->rowCount(currentIndex) == 1)
{
currentIndex = m_propListModel->index(0, 0, currentIndex); currentIndex = m_propListModel->index(0, 0, currentIndex);
m_ui->filesList->setExpanded(currentIndex, true); m_ui->filesList->setExpanded(currentIndex, true);
} }
@ -350,7 +354,8 @@ void PropertiesWidget::readSettings()
const Preferences *const pref = Preferences::instance(); const Preferences *const pref = Preferences::instance();
// Restore splitter sizes // Restore splitter sizes
QStringList sizesStr = pref->getPropSplitterSizes().split(','); QStringList sizesStr = pref->getPropSplitterSizes().split(',');
if (sizesStr.size() == 2) { if (sizesStr.size() == 2)
{
m_slideSizes << sizesStr.first().toInt(); m_slideSizes << sizesStr.first().toInt();
m_slideSizes << sizesStr.last().toInt(); m_slideSizes << sizesStr.last().toInt();
auto *hSplitter = static_cast<QSplitter *>(parentWidget()); auto *hSplitter = static_cast<QSplitter *>(parentWidget());
@ -396,8 +401,10 @@ void PropertiesWidget::loadDynamicData()
if (!m_torrent || (m_state != VISIBLE)) return; if (!m_torrent || (m_state != VISIBLE)) return;
// Transfer infos // Transfer infos
switch (m_ui->stackedProperties->currentIndex()) { switch (m_ui->stackedProperties->currentIndex())
case PropTabBar::MainTab: { {
case PropTabBar::MainTab:
{
m_ui->labelWastedVal->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize())); m_ui->labelWastedVal->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize()));
m_ui->labelUpTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload()) m_ui->labelUpTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload())
@ -456,16 +463,19 @@ void PropertiesWidget::loadDynamicData()
m_ui->labelAddedOnVal->setText(m_torrent->addedTime().toString(Qt::DefaultLocaleShortDate)); m_ui->labelAddedOnVal->setText(m_torrent->addedTime().toString(Qt::DefaultLocaleShortDate));
if (m_torrent->hasMetadata()) { if (m_torrent->hasMetadata())
{
m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave())); m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave()));
if (!m_torrent->isSeed() && !m_torrent->isPaused() && !m_torrent->isQueued() && !m_torrent->isChecking()) { if (!m_torrent->isSeed() && !m_torrent->isPaused() && !m_torrent->isQueued() && !m_torrent->isChecking())
{
// Pieces availability // Pieces availability
showPiecesAvailability(true); showPiecesAvailability(true);
m_piecesAvailability->setAvailability(m_torrent->pieceAvailability()); m_piecesAvailability->setAvailability(m_torrent->pieceAvailability());
m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3)); m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3));
} }
else { else
{
showPiecesAvailability(false); showPiecesAvailability(false);
} }
@ -474,7 +484,8 @@ void PropertiesWidget::loadDynamicData()
m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + '%'); m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + '%');
m_downloadedPieces->setProgress(m_torrent->pieces(), m_torrent->downloadingPieces()); m_downloadedPieces->setProgress(m_torrent->pieces(), m_torrent->downloadingPieces());
} }
else { else
{
showPiecesAvailability(false); showPiecesAvailability(false);
} }
} }
@ -489,7 +500,8 @@ void PropertiesWidget::loadDynamicData()
break; break;
case PropTabBar::FilesTab: case PropTabBar::FilesTab:
// Files progress // Files progress
if (m_torrent->hasMetadata()) { if (m_torrent->hasMetadata())
{
qDebug("Updating priorities in files tab"); qDebug("Updating priorities in files tab");
m_ui->filesList->setUpdatesEnabled(false); m_ui->filesList->setUpdatesEnabled(false);
m_propListModel->model()->updateFilesProgress(m_torrent->filesProgress()); m_propListModel->model()->updateFilesProgress(m_torrent->filesProgress());
@ -511,7 +523,8 @@ void PropertiesWidget::loadUrlSeeds()
qDebug("Loading URL seeds"); qDebug("Loading URL seeds");
const QVector<QUrl> hcSeeds = m_torrent->urlSeeds(); const QVector<QUrl> hcSeeds = m_torrent->urlSeeds();
// Add url seeds // Add url seeds
for (const QUrl &hcSeed : hcSeeds) { for (const QUrl &hcSeed : hcSeeds)
{
qDebug("Loading URL seed: %s", qUtf8Printable(hcSeed.toString())); qDebug("Loading URL seed: %s", qUtf8Printable(hcSeed.toString()));
new QListWidgetItem(hcSeed.toString(), m_ui->listWebSeeds); new QListWidgetItem(hcSeed.toString(), m_ui->listWebSeeds);
} }
@ -519,7 +532,8 @@ void PropertiesWidget::loadUrlSeeds()
QString PropertiesWidget::getFullPath(const QModelIndex &index) const QString PropertiesWidget::getFullPath(const QModelIndex &index) const
{ {
if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType) { if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType)
{
const int fileIdx = m_propListModel->getFileIndex(index); const int fileIdx = m_propListModel->getFileIndex(index);
const QString filename {m_torrent->filePath(fileIdx)}; const QString filename {m_torrent->filePath(fileIdx)};
const QDir saveDir {m_torrent->savePath(true)}; const QDir saveDir {m_torrent->savePath(true)};
@ -568,7 +582,8 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1) { if (selectedRows.size() == 1)
{
const QModelIndex index = selectedRows[0]; const QModelIndex index = selectedRows[0];
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")); const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open"));
@ -583,12 +598,14 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
menu->addSeparator(); menu->addSeparator();
} }
if (!m_torrent->isSeed()) { if (!m_torrent->isSeed())
{
QMenu *subMenu = menu->addMenu(tr("Priority")); QMenu *subMenu = menu->addMenu(tr("Priority"));
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio) const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
{ {
for (const QModelIndex &index : selectedRows) { for (const QModelIndex &index : selectedRows)
{
m_propListModel->setData( m_propListModel->setData(
m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio)); m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
} }
@ -646,7 +663,8 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed")); const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"));
connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed); connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed);
if (!rows.isEmpty()) { if (!rows.isEmpty())
{
const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")); const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed"));
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds); connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds);
@ -673,16 +691,20 @@ void PropertiesWidget::openSelectedFile()
void PropertiesWidget::configure() void PropertiesWidget::configure()
{ {
// Speed widget // Speed widget
if (Preferences::instance()->isSpeedWidgetEnabled()) { if (Preferences::instance()->isSpeedWidgetEnabled())
if (!m_speedWidget || !qobject_cast<SpeedWidget *>(m_speedWidget)) { {
if (!m_speedWidget || !qobject_cast<SpeedWidget *>(m_speedWidget))
{
m_ui->speedLayout->removeWidget(m_speedWidget); m_ui->speedLayout->removeWidget(m_speedWidget);
delete m_speedWidget; delete m_speedWidget;
m_speedWidget = new SpeedWidget {this}; m_speedWidget = new SpeedWidget {this};
m_ui->speedLayout->addWidget(m_speedWidget); m_ui->speedLayout->addWidget(m_speedWidget);
} }
} }
else { else
if (!m_speedWidget || !qobject_cast<QLabel *>(m_speedWidget)) { {
if (!m_speedWidget || !qobject_cast<QLabel *>(m_speedWidget))
{
m_ui->speedLayout->removeWidget(m_speedWidget); m_ui->speedLayout->removeWidget(m_speedWidget);
delete m_speedWidget; delete m_speedWidget;
auto *label = new QLabel(tr("<center><b>Speed graphs are disabled</b><p>You may change this setting in Advanced Options </center>"), this); auto *label = new QLabel(tr("<center><b>Speed graphs are disabled</b><p>You may change this setting in Advanced Options </center>"), this);
@ -702,7 +724,8 @@ void PropertiesWidget::askWebSeed()
QLatin1String("http://www."), &ok); QLatin1String("http://www."), &ok);
if (!ok) return; if (!ok) return;
qDebug("Adding %s web seed", qUtf8Printable(urlSeed)); qDebug("Adding %s web seed", qUtf8Printable(urlSeed));
if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty()) { if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty())
{
QMessageBox::warning(this, "qBittorrent", QMessageBox::warning(this, "qBittorrent",
tr("This URL seed is already in the list."), tr("This URL seed is already in the list."),
QMessageBox::Ok); QMessageBox::Ok);
@ -755,7 +778,8 @@ void PropertiesWidget::editWebSeed()
oldSeed, &result); oldSeed, &result);
if (!result) return; if (!result) return;
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty()) { if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
{
QMessageBox::warning(this, tr("qBittorrent"), QMessageBox::warning(this, tr("qBittorrent"),
tr("This URL seed is already in the list."), tr("This URL seed is already in the list."),
QMessageBox::Ok); QMessageBox::Ok);
@ -781,11 +805,13 @@ void PropertiesWidget::filteredFilesChanged()
void PropertiesWidget::filterText(const QString &filter) void PropertiesWidget::filterText(const QString &filter)
{ {
m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix)); m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix));
if (filter.isEmpty()) { if (filter.isEmpty())
{
m_ui->filesList->collapseAll(); m_ui->filesList->collapseAll();
m_ui->filesList->expand(m_propListModel->index(0, 0)); m_ui->filesList->expand(m_propListModel->index(0, 0));
} }
else { else
{
m_ui->filesList->expandAll(); m_ui->filesList->expandAll();
} }
} }

View file

@ -72,11 +72,13 @@ void PropListDelegate::initProgressStyleOption(QStyleOptionProgressBar &option,
ProgressBarDelegate::initProgressStyleOption(option, index); ProgressBarDelegate::initProgressStyleOption(option, index);
const int priority const int priority
= index.sibling(index.row(), PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt(); = index.sibling(index.row(), PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
if (static_cast<BitTorrent::DownloadPriority>(priority) == BitTorrent::DownloadPriority::Ignored) { if (static_cast<BitTorrent::DownloadPriority>(priority) == BitTorrent::DownloadPriority::Ignored)
{
option.state &= ~QStyle::State_Enabled; option.state &= ~QStyle::State_Enabled;
option.palette = progressBarDisabledPalette(); option.palette = progressBarDisabledPalette();
} }
else { else
{
option.state |= QStyle::State_Enabled; option.state |= QStyle::State_Enabled;
} }
} }
@ -86,7 +88,8 @@ void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
auto *combobox = static_cast<QComboBox *>(editor); auto *combobox = static_cast<QComboBox *>(editor);
// Set combobox index // Set combobox index
const int priority = index.data(TorrentContentModel::UnderlyingDataRole).toInt(); const int priority = index.data(TorrentContentModel::UnderlyingDataRole).toInt();
switch (static_cast<BitTorrent::DownloadPriority>(priority)) { switch (static_cast<BitTorrent::DownloadPriority>(priority))
{
case BitTorrent::DownloadPriority::Ignored: case BitTorrent::DownloadPriority::Ignored:
combobox->setCurrentIndex(0); combobox->setCurrentIndex(0);
break; break;
@ -106,7 +109,8 @@ QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
{ {
if (index.column() != PRIORITY) return nullptr; if (index.column() != PRIORITY) return nullptr;
if (m_properties) { if (m_properties)
{
const BitTorrent::TorrentHandle *torrent = m_properties->getCurrentTorrent(); const BitTorrent::TorrentHandle *torrent = m_properties->getCurrentTorrent();
if (!torrent || !torrent->hasMetadata() || torrent->isSeed()) if (!torrent || !torrent->hasMetadata() || torrent->isSeed())
return nullptr; return nullptr;
@ -131,7 +135,8 @@ void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const int value = combobox->currentIndex(); const int value = combobox->currentIndex();
BitTorrent::DownloadPriority prio = BitTorrent::DownloadPriority::Normal; // NORMAL BitTorrent::DownloadPriority prio = BitTorrent::DownloadPriority::Normal; // NORMAL
switch (value) { switch (value)
{
case 0: case 0:
prio = BitTorrent::DownloadPriority::Ignored; // IGNORED prio = BitTorrent::DownloadPriority::Ignored; // IGNORED
break; break;

View file

@ -122,8 +122,10 @@ void PropTabBar::setCurrentIndex(int index)
if (index >= m_btnGroup->buttons().size()) if (index >= m_btnGroup->buttons().size())
index = 0; index = 0;
// If asked to hide or if the currently selected tab is clicked // If asked to hide or if the currently selected tab is clicked
if ((index < 0) || (m_currentIndex == index)) { if ((index < 0) || (m_currentIndex == index))
if (m_currentIndex >= 0) { {
if (m_currentIndex >= 0)
{
m_btnGroup->button(m_currentIndex)->setDown(false); m_btnGroup->button(m_currentIndex)->setDown(false);
m_currentIndex = -1; m_currentIndex = -1;
emit visibilityToggled(false); emit visibilityToggled(false);
@ -131,10 +133,12 @@ void PropTabBar::setCurrentIndex(int index)
return; return;
} }
// Unselect previous tab // Unselect previous tab
if (m_currentIndex >= 0) { if (m_currentIndex >= 0)
{
m_btnGroup->button(m_currentIndex)->setDown(false); m_btnGroup->button(m_currentIndex)->setDown(false);
} }
else { else
{
// Nothing was selected, show! // Nothing was selected, show!
emit visibilityToggled(true); emit visibilityToggled(true);
} }

View file

@ -79,26 +79,30 @@ namespace
if (value <= 12.0) return {12, SizeUnit::Byte}; if (value <= 12.0) return {12, SizeUnit::Byte};
SizeUnit calculatedUnit = SizeUnit::Byte; SizeUnit calculatedUnit = SizeUnit::Byte;
while (value > 1024) { while (value > 1024)
{
value /= 1024; value /= 1024;
calculatedUnit = static_cast<SizeUnit>(static_cast<int>(calculatedUnit) + 1); calculatedUnit = static_cast<SizeUnit>(static_cast<int>(calculatedUnit) + 1);
} }
if (value > 100.0) { if (value > 100.0)
{
int roundedValue = static_cast<int>(value / 40) * 40; int roundedValue = static_cast<int>(value / 40) * 40;
while (roundedValue < value) while (roundedValue < value)
roundedValue += 40; roundedValue += 40;
return {static_cast<double>(roundedValue), calculatedUnit}; return {static_cast<double>(roundedValue), calculatedUnit};
} }
if (value > 10.0) { if (value > 10.0)
{
int roundedValue = static_cast<int>(value / 4) * 4; int roundedValue = static_cast<int>(value / 4) * 4;
while (roundedValue < value) while (roundedValue < value)
roundedValue += 4; roundedValue += 4;
return {static_cast<double>(roundedValue), calculatedUnit}; return {static_cast<double>(roundedValue), calculatedUnit};
} }
for (const auto &roundedValue : roundingTable) { for (const auto &roundedValue : roundingTable)
{
if (value <= roundedValue) if (value <= roundedValue)
return {roundedValue, calculatedUnit}; return {roundedValue, calculatedUnit};
} }
@ -215,7 +219,8 @@ void SpeedPlotView::setPeriod(const TimePeriod period)
{ {
m_period = period; m_period = period;
switch (period) { switch (period)
{
case SpeedPlotView::MIN1: case SpeedPlotView::MIN1:
m_viewablePointsCount = MIN1_SEC; m_viewablePointsCount = MIN1_SEC;
m_currentData = &m_data5Min; m_currentData = &m_data5Min;
@ -266,7 +271,8 @@ quint64 SpeedPlotView::maxYValue()
boost::circular_buffer<PointData> &queue = getCurrentData(); boost::circular_buffer<PointData> &queue = getCurrentData();
quint64 maxYValue = 0; quint64 maxYValue = 0;
for (int id = UP; id < NB_GRAPHS; ++id) { for (int id = UP; id < NB_GRAPHS; ++id)
{
if (!m_properties[static_cast<GraphID>(id)].enable) if (!m_properties[static_cast<GraphID>(id)].enable)
continue; continue;
@ -292,7 +298,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
rect.adjust(0, fontMetrics.height(), 0, 0); // Add top padding for top speed text rect.adjust(0, fontMetrics.height(), 0, 0); // Add top padding for top speed text
// draw Y axis speed labels // draw Y axis speed labels
const QVector<QString> speedLabels = { const QVector<QString> speedLabels =
{
formatLabel(niceScale.arg, niceScale.unit), formatLabel(niceScale.arg, niceScale.unit),
formatLabel((0.75 * niceScale.arg), niceScale.unit), formatLabel((0.75 * niceScale.arg), niceScale.unit),
formatLabel((0.50 * niceScale.arg), niceScale.unit), formatLabel((0.50 * niceScale.arg), niceScale.unit),
@ -311,7 +318,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
#endif #endif
int i = 0; int i = 0;
for (const QString &label : speedLabels) { for (const QString &label : speedLabels)
{
QRectF labelRect(rect.topLeft() + QPointF(-yAxisWidth, (i++) * 0.25 * rect.height() - fontMetrics.height()), QRectF labelRect(rect.topLeft() + QPointF(-yAxisWidth, (i++) * 0.25 * rect.height() - fontMetrics.height()),
QSizeF(2 * yAxisWidth, fontMetrics.height())); QSizeF(2 * yAxisWidth, fontMetrics.height()));
painter.drawText(labelRect, label, Qt::AlignRight | Qt::AlignTop); painter.drawText(labelRect, label, Qt::AlignRight | Qt::AlignTop);
@ -333,7 +341,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
painter.drawLine(fullRect.left(), rect.bottom(), rect.right(), rect.bottom()); painter.drawLine(fullRect.left(), rect.bottom(), rect.right(), rect.bottom());
const int TIME_AXIS_DIVISIONS = 6; const int TIME_AXIS_DIVISIONS = 6;
for (int i = 0; i < TIME_AXIS_DIVISIONS; ++i) { for (int i = 0; i < TIME_AXIS_DIVISIONS; ++i)
{
const int x = rect.left() + (i * rect.width()) / TIME_AXIS_DIVISIONS; const int x = rect.left() + (i * rect.width()) / TIME_AXIS_DIVISIONS;
painter.drawLine(x, fullRect.top(), x, fullRect.bottom()); painter.drawLine(x, fullRect.top(), x, fullRect.bottom());
} }
@ -349,12 +358,14 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
boost::circular_buffer<PointData> &queue = getCurrentData(); boost::circular_buffer<PointData> &queue = getCurrentData();
for (int id = UP; id < NB_GRAPHS; ++id) { for (int id = UP; id < NB_GRAPHS; ++id)
{
if (!m_properties[static_cast<GraphID>(id)].enable) if (!m_properties[static_cast<GraphID>(id)].enable)
continue; continue;
QVector<QPoint> points; QVector<QPoint> points;
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j) { for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
{
int newX = rect.right() - j * xTickSize; int newX = rect.right() - j * xTickSize;
int newY = rect.bottom() - queue[i].y[id] * yMultiplier; int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
@ -371,7 +382,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
double legendHeight = 0; double legendHeight = 0;
int legendWidth = 0; int legendWidth = 0;
for (const auto &property : asConst(m_properties)) { for (const auto &property : asConst(m_properties))
{
if (!property.enable) if (!property.enable)
continue; continue;
@ -391,7 +403,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
painter.fillRect(legendBackgroundRect, legendBackgroundColor); painter.fillRect(legendBackgroundRect, legendBackgroundColor);
i = 0; i = 0;
for (const auto &property : asConst(m_properties)) { for (const auto &property : asConst(m_properties))
{
if (!property.enable) if (!property.enable)
continue; continue;

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