mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-11-22 01:06:03 +03:00
Improve coding style
This commit is contained in:
parent
acad35c5bc
commit
c41df9ffbd
147 changed files with 4454 additions and 2227 deletions
|
@ -47,7 +47,7 @@ int myFunction(int a)
|
|||
void myFunction() {} // empty body
|
||||
|
||||
MyClass::MyClass(int *parent)
|
||||
: m_parent(parent)
|
||||
: m_parent {parent}
|
||||
{
|
||||
// initialize
|
||||
}
|
||||
|
@ -86,15 +86,18 @@ namespace Name
|
|||
### b. Other code blocks
|
||||
|
||||
```c++
|
||||
if (condition) {
|
||||
if (condition)
|
||||
{
|
||||
// code
|
||||
}
|
||||
|
||||
for (int a = 0; a < b; ++b) {
|
||||
for (int a = 0; a < b; ++b)
|
||||
{
|
||||
// code
|
||||
}
|
||||
|
||||
switch (a) {
|
||||
switch (a)
|
||||
{
|
||||
case 1:
|
||||
// blah
|
||||
case 2:
|
||||
|
@ -102,18 +105,25 @@ case 2:
|
|||
default:
|
||||
// blah
|
||||
}
|
||||
|
||||
{
|
||||
// code
|
||||
}
|
||||
```
|
||||
|
||||
### c. Blocks in switch's case labels
|
||||
|
||||
```c++
|
||||
switch (var) {
|
||||
case 1: {
|
||||
switch (var)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
// declare local variables
|
||||
// code
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
case 2:
|
||||
{
|
||||
// declare local variables
|
||||
// code
|
||||
}
|
||||
|
@ -128,13 +138,16 @@ default:
|
|||
The `else if`/`else` must be on their own lines:
|
||||
|
||||
```c++
|
||||
if (condition) {
|
||||
if (condition)
|
||||
{
|
||||
// code
|
||||
}
|
||||
else if (condition) {
|
||||
else if (condition)
|
||||
{
|
||||
// code
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// code
|
||||
}
|
||||
```
|
||||
|
@ -155,7 +168,8 @@ However you can still choose to use the first rule.
|
|||
```c++
|
||||
if (a > 0) return;
|
||||
|
||||
while (p) {
|
||||
while (p)
|
||||
{
|
||||
// ...
|
||||
if (!b) continue;
|
||||
}
|
||||
|
@ -178,14 +192,18 @@ else if (a > b)
|
|||
else
|
||||
do(c);
|
||||
|
||||
if (a < b) {
|
||||
if (a < b)
|
||||
{
|
||||
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(d);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
do(c);
|
||||
}
|
||||
```
|
||||
|
@ -216,10 +234,10 @@ Initialization lists should be vertical. This will allow for more easily readabl
|
|||
|
||||
```c++
|
||||
myClass::myClass(int a, int b, int c, int d)
|
||||
: m_a(a)
|
||||
, m_b(b)
|
||||
, m_c(c)
|
||||
, m_d(d)
|
||||
: m_a {a}
|
||||
, m_b {b}
|
||||
, m_c {c}
|
||||
, m_d {d}
|
||||
{
|
||||
// code
|
||||
}
|
||||
|
@ -396,13 +414,15 @@ class ExampleWidget : public QWidget
|
|||
template <typename 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
|
||||
// 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,
|
||||
// because all iterators have the same interface
|
||||
}
|
||||
|
@ -420,10 +440,12 @@ class ExampleWidget : public QWidget
|
|||
a = (b <= MAX_B ? b : MAX_B);
|
||||
++a;
|
||||
--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
|
||||
for (auto i : container) {
|
||||
for (auto i : container)
|
||||
{
|
||||
}
|
||||
// Derived class, spaces before and after the colon
|
||||
class Derived : public Base
|
||||
|
|
|
@ -181,12 +181,14 @@ Application::Application(int &argc, char **argv)
|
|||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
|
||||
Logger::instance()->addMessage(tr("qBittorrent %1 started", "qBittorrent v3.2.0alpha started").arg(QBT_VERSION));
|
||||
if (portableModeEnabled) {
|
||||
if (portableModeEnabled)
|
||||
{
|
||||
Logger::instance()->addMessage(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir));
|
||||
if (m_commandLineArgs.relativeFastresumePaths)
|
||||
Logger::instance()->addMessage(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg("--relative-fastresume"), Log::WARNING); // to avoid translating the `--relative-fastresume` string
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args)
|
||||
{
|
||||
if (Preferences::instance()->isAutoRunConsoleEnabled()) {
|
||||
if (Preferences::instance()->isAutoRunConsoleEnabled())
|
||||
{
|
||||
args->flags |= CREATE_NEW_CONSOLE;
|
||||
args->flags &= ~(CREATE_NO_WINDOW | DETACHED_PROCESS);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
args->flags |= CREATE_NO_WINDOW;
|
||||
args->flags &= ~(CREATE_NEW_CONSOLE | DETACHED_PROCESS);
|
||||
}
|
||||
|
@ -428,7 +432,8 @@ void Application::torrentFinished(BitTorrent::TorrentHandle *const torrent)
|
|||
runExternalProgram(torrent);
|
||||
|
||||
// Mail notification
|
||||
if (pref->isMailNotificationEnabled()) {
|
||||
if (pref->isMailNotificationEnabled())
|
||||
{
|
||||
Logger::instance()->addMessage(tr("Torrent: %1, sending mail notification").arg(torrent->name()));
|
||||
sendNotificationEmail(torrent);
|
||||
}
|
||||
|
@ -455,16 +460,19 @@ void Application::allTorrentsFinished()
|
|||
|
||||
#ifndef DISABLE_GUI
|
||||
// ask confirm
|
||||
if ((action == ShutdownDialogAction::Exit) && (pref->dontConfirmAutoExit())) {
|
||||
if ((action == ShutdownDialogAction::Exit) && (pref->dontConfirmAutoExit()))
|
||||
{
|
||||
// do nothing & skip confirm
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return;
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
// Actually shut down
|
||||
if (action != ShutdownDialogAction::Exit) {
|
||||
if (action != ShutdownDialogAction::Exit)
|
||||
{
|
||||
qDebug("Preparing for auto-shutdown because all downloads are complete!");
|
||||
// Disabling it for next time
|
||||
pref->setShutdownWhenDownloadsComplete(false);
|
||||
|
@ -490,7 +498,8 @@ bool Application::sendParams(const QStringList ¶ms)
|
|||
void Application::processParams(const QStringList ¶ms)
|
||||
{
|
||||
#ifndef DISABLE_GUI
|
||||
if (params.isEmpty()) {
|
||||
if (params.isEmpty())
|
||||
{
|
||||
m_window->activate(); // show UI
|
||||
return;
|
||||
}
|
||||
|
@ -498,42 +507,50 @@ void Application::processParams(const QStringList ¶ms)
|
|||
BitTorrent::AddTorrentParams torrentParams;
|
||||
TriStateBool skipTorrentDialog;
|
||||
|
||||
for (QString param : params) {
|
||||
for (QString param : params)
|
||||
{
|
||||
param = param.trimmed();
|
||||
|
||||
// Process strings indicating options specified by the user.
|
||||
|
||||
if (param.startsWith(QLatin1String("@savePath="))) {
|
||||
if (param.startsWith(QLatin1String("@savePath=")))
|
||||
{
|
||||
torrentParams.savePath = param.mid(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(QLatin1String("@addPaused="))) {
|
||||
if (param.startsWith(QLatin1String("@addPaused=")))
|
||||
{
|
||||
torrentParams.addPaused = param.midRef(11).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == QLatin1String("@skipChecking")) {
|
||||
if (param == QLatin1String("@skipChecking"))
|
||||
{
|
||||
torrentParams.skipChecking = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(QLatin1String("@category="))) {
|
||||
if (param.startsWith(QLatin1String("@category=")))
|
||||
{
|
||||
torrentParams.category = param.mid(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == QLatin1String("@sequential")) {
|
||||
if (param == QLatin1String("@sequential"))
|
||||
{
|
||||
torrentParams.sequential = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == QLatin1String("@firstLastPiecePriority")) {
|
||||
if (param == QLatin1String("@firstLastPiecePriority"))
|
||||
{
|
||||
torrentParams.firstLastPiecePriority = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(QLatin1String("@skipDialog="))) {
|
||||
if (param.startsWith(QLatin1String("@skipDialog=")))
|
||||
{
|
||||
skipTorrentDialog = param.midRef(12).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
continue;
|
||||
}
|
||||
|
@ -561,7 +578,8 @@ int Application::exec(const QStringList ¶ms)
|
|||
Net::DownloadManager::initInstance();
|
||||
IconProvider::initInstance();
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
BitTorrent::Session::initInstance();
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
||||
|
@ -581,7 +599,8 @@ int Application::exec(const QStringList ¶ms)
|
|||
new RSS::Session; // create RSS::Session singleton
|
||||
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
|
||||
}
|
||||
catch (const RuntimeError &err) {
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
fprintf(stderr, "%s", err.what());
|
||||
#else
|
||||
|
@ -605,7 +624,8 @@ int Application::exec(const QStringList ¶ms)
|
|||
.arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n';
|
||||
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'
|
||||
+ 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';
|
||||
|
@ -623,7 +643,8 @@ int Application::exec(const QStringList ¶ms)
|
|||
BitTorrent::Session::instance()->startUpTorrents();
|
||||
|
||||
m_paramsQueue = params + m_paramsQueue;
|
||||
if (!m_paramsQueue.isEmpty()) {
|
||||
if (!m_paramsQueue.isEmpty())
|
||||
{
|
||||
processParams(m_paramsQueue);
|
||||
m_paramsQueue.clear();
|
||||
}
|
||||
|
@ -639,7 +660,8 @@ bool Application::isRunning()
|
|||
#ifdef Q_OS_MACOS
|
||||
bool Application::event(QEvent *ev)
|
||||
{
|
||||
if (ev->type() == QEvent::FileOpen) {
|
||||
if (ev->type() == QEvent::FileOpen)
|
||||
{
|
||||
QString path = static_cast<QFileOpenEvent *>(ev)->file();
|
||||
if (path.isEmpty())
|
||||
// Get the url instead
|
||||
|
@ -651,7 +673,8 @@ bool Application::event(QEvent *ev)
|
|||
m_paramsQueue.append(path);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
return BaseApplication::event(ev);
|
||||
}
|
||||
}
|
||||
|
@ -679,11 +702,13 @@ void Application::initializeTranslation()
|
|||
installTranslator(&m_translator);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (localeStr.startsWith("ar") || localeStr.startsWith("he")) {
|
||||
if (localeStr.startsWith("ar") || localeStr.startsWith("he"))
|
||||
{
|
||||
qDebug("Right to Left mode");
|
||||
setLayoutDirection(Qt::RightToLeft);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
setLayoutDirection(Qt::LeftToRight);
|
||||
}
|
||||
#endif
|
||||
|
@ -725,7 +750,8 @@ void Application::cleanup()
|
|||
return;
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (m_window) {
|
||||
if (m_window)
|
||||
{
|
||||
// Hide the window and don't leave it on screen as
|
||||
// unresponsive. Also for Windows take the WinId
|
||||
// after it's hidden, because hide() may cause a
|
||||
|
@ -769,7 +795,8 @@ void Application::cleanup()
|
|||
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (m_window) {
|
||||
if (m_window)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
|
||||
#endif // Q_OS_WIN
|
||||
|
@ -780,7 +807,8 @@ void Application::cleanup()
|
|||
|
||||
Profile::freeInstance();
|
||||
|
||||
if (m_shutdownAct != ShutdownDialogAction::Exit) {
|
||||
if (m_shutdownAct != ShutdownDialogAction::Exit)
|
||||
{
|
||||
qDebug() << "Sending computer shutdown/suspend/hibernate signal...";
|
||||
Utils::Misc::shutdownComputer(m_shutdownAct);
|
||||
}
|
||||
|
|
|
@ -46,16 +46,20 @@ ApplicationInstanceManager::ApplicationInstanceManager(const QString &appId, QOb
|
|||
|
||||
#ifdef Q_OS_WIN
|
||||
auto sharedMem = new QSharedMemory {appId + QLatin1String {"-shared-memory-key"}, this};
|
||||
if (m_isFirstInstance) {
|
||||
if (m_isFirstInstance)
|
||||
{
|
||||
// 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();
|
||||
sharedMem->unlock();
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// 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())));
|
||||
sharedMem->unlock();
|
||||
}
|
||||
|
|
|
@ -217,7 +217,8 @@ namespace
|
|||
|
||||
bool ok;
|
||||
int res = val.toInt(&ok);
|
||||
if (!ok) {
|
||||
if (!ok)
|
||||
{
|
||||
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
|
||||
.arg(envVarName(), val);
|
||||
return defaultValue;
|
||||
|
@ -257,16 +258,20 @@ namespace
|
|||
{
|
||||
QStringList parts = arg.split(QLatin1Char('='));
|
||||
|
||||
if (parts.size() == 1) {
|
||||
if (parts.size() == 1)
|
||||
{
|
||||
return TriStateBool(m_defaultValue);
|
||||
}
|
||||
if (parts.size() == 2) {
|
||||
if (parts.size() == 2)
|
||||
{
|
||||
QString val = parts[1];
|
||||
|
||||
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
|
||||
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1")))
|
||||
{
|
||||
return TriStateBool::True;
|
||||
}
|
||||
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
|
||||
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
|
||||
{
|
||||
return TriStateBool::False;
|
||||
}
|
||||
}
|
||||
|
@ -281,16 +286,20 @@ namespace
|
|||
{
|
||||
const QString val = env.value(envVarName(), "-1");
|
||||
|
||||
if (val.isEmpty()) {
|
||||
if (val.isEmpty())
|
||||
{
|
||||
return TriStateBool(m_defaultValue);
|
||||
}
|
||||
if (val == QLatin1String("-1")) {
|
||||
if (val == QLatin1String("-1"))
|
||||
{
|
||||
return TriStateBool::Undefined;
|
||||
}
|
||||
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
|
||||
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1")))
|
||||
{
|
||||
return TriStateBool::True;
|
||||
}
|
||||
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
|
||||
if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0")))
|
||||
{
|
||||
return TriStateBool::False;
|
||||
}
|
||||
|
||||
|
@ -365,10 +374,12 @@ QStringList QBtCommandLineParameters::paramList() const
|
|||
if (!savePath.isEmpty())
|
||||
result.append(QLatin1String("@savePath=") + savePath);
|
||||
|
||||
if (addPaused == TriStateBool::True) {
|
||||
if (addPaused == TriStateBool::True)
|
||||
{
|
||||
result.append(QLatin1String("@addPaused=1"));
|
||||
}
|
||||
else if (addPaused == TriStateBool::False) {
|
||||
else if (addPaused == TriStateBool::False)
|
||||
{
|
||||
result.append(QLatin1String("@addPaused=0"));
|
||||
}
|
||||
|
||||
|
@ -384,10 +395,12 @@ QStringList QBtCommandLineParameters::paramList() const
|
|||
if (firstLastPiecePriority)
|
||||
result.append(QLatin1String("@firstLastPiecePriority"));
|
||||
|
||||
if (skipDialog == TriStateBool::True) {
|
||||
if (skipDialog == TriStateBool::True)
|
||||
{
|
||||
result.append(QLatin1String("@skipDialog=1"));
|
||||
}
|
||||
else if (skipDialog == TriStateBool::False) {
|
||||
else if (skipDialog == TriStateBool::False)
|
||||
{
|
||||
result.append(QLatin1String("@skipDialog=0"));
|
||||
}
|
||||
|
||||
|
@ -399,72 +412,91 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
|||
{
|
||||
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];
|
||||
|
||||
if ((arg.startsWith("--") && !arg.endsWith(".torrent"))
|
||||
|| (arg.startsWith('-') && (arg.size() == 2))) {
|
||||
|| (arg.startsWith('-') && (arg.size() == 2)))
|
||||
{
|
||||
// Parse known parameters
|
||||
if (arg == SHOW_HELP_OPTION) {
|
||||
if (arg == SHOW_HELP_OPTION)
|
||||
{
|
||||
result.showHelp = true;
|
||||
}
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
else if (arg == SHOW_VERSION_OPTION) {
|
||||
else if (arg == SHOW_VERSION_OPTION)
|
||||
{
|
||||
result.showVersion = true;
|
||||
}
|
||||
#endif
|
||||
else if (arg == WEBUI_PORT_OPTION) {
|
||||
else if (arg == WEBUI_PORT_OPTION)
|
||||
{
|
||||
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
|
||||
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
|
||||
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
|
||||
.arg(QLatin1String("--webui-port")));
|
||||
}
|
||||
#ifndef DISABLE_GUI
|
||||
else if (arg == NO_SPLASH_OPTION) {
|
||||
else if (arg == NO_SPLASH_OPTION)
|
||||
{
|
||||
result.noSplash = true;
|
||||
}
|
||||
#elif !defined(Q_OS_WIN)
|
||||
else if (arg == DAEMON_OPTION) {
|
||||
else if (arg == DAEMON_OPTION)
|
||||
{
|
||||
result.shouldDaemonize = true;
|
||||
}
|
||||
#endif
|
||||
else if (arg == PROFILE_OPTION) {
|
||||
else if (arg == PROFILE_OPTION)
|
||||
{
|
||||
result.profileDir = PROFILE_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == RELATIVE_FASTRESUME) {
|
||||
else if (arg == RELATIVE_FASTRESUME)
|
||||
{
|
||||
result.relativeFastresumePaths = true;
|
||||
}
|
||||
else if (arg == CONFIGURATION_OPTION) {
|
||||
else if (arg == CONFIGURATION_OPTION)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else if (arg == PAUSED_OPTION) {
|
||||
else if (arg == PAUSED_OPTION)
|
||||
{
|
||||
result.addPaused = PAUSED_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == SKIP_HASH_CHECK_OPTION) {
|
||||
else if (arg == SKIP_HASH_CHECK_OPTION)
|
||||
{
|
||||
result.skipChecking = true;
|
||||
}
|
||||
else if (arg == CATEGORY_OPTION) {
|
||||
else if (arg == CATEGORY_OPTION)
|
||||
{
|
||||
result.category = CATEGORY_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == SEQUENTIAL_OPTION) {
|
||||
else if (arg == SEQUENTIAL_OPTION)
|
||||
{
|
||||
result.sequential = true;
|
||||
}
|
||||
else if (arg == FIRST_AND_LAST_OPTION) {
|
||||
else if (arg == FIRST_AND_LAST_OPTION)
|
||||
{
|
||||
result.firstLastPiecePriority = true;
|
||||
}
|
||||
else if (arg == SKIP_DIALOG_OPTION) {
|
||||
else if (arg == SKIP_DIALOG_OPTION)
|
||||
{
|
||||
result.skipDialog = SKIP_DIALOG_OPTION.value(arg);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Unknown argument
|
||||
result.unknownParameter = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QFileInfo torrentPath;
|
||||
torrentPath.setFile(arg);
|
||||
|
||||
|
@ -495,11 +527,14 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
|
|||
QStringList lines = {words.first()};
|
||||
int currentLineMaxLength = wrapAtColumn - initialIndentation;
|
||||
|
||||
for (const QString &word : asConst(words.mid(1))) {
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength) {
|
||||
for (const QString &word : asConst(words.mid(1)))
|
||||
{
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
|
||||
{
|
||||
lines.last().append(' ' + word);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
lines.append(QString(initialIndentation, ' ') + word);
|
||||
currentLineMaxLength = wrapAtColumn;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,8 @@ void FileLogger::changePath(const QString &newPath)
|
|||
dir.mkpath(newPath);
|
||||
const QString tmpPath = dir.absoluteFilePath("qbittorrent.log");
|
||||
|
||||
if (tmpPath != m_path) {
|
||||
if (tmpPath != m_path)
|
||||
{
|
||||
m_path = tmpPath;
|
||||
|
||||
closeLogFile();
|
||||
|
@ -89,9 +90,11 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
|||
const QFileInfoList fileList = dir.entryInfoList(QStringList("qbittorrent.log.bak*")
|
||||
, (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed));
|
||||
|
||||
for (const QFileInfo &file : fileList) {
|
||||
for (const QFileInfo &file : fileList)
|
||||
{
|
||||
QDateTime modificationDate = file.lastModified();
|
||||
switch (ageType) {
|
||||
switch (ageType)
|
||||
{
|
||||
case DAYS:
|
||||
modificationDate = modificationDate.addDays(age);
|
||||
break;
|
||||
|
@ -124,7 +127,8 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
|
|||
QTextStream stream(&m_logFile);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
switch (msg.type) {
|
||||
switch (msg.type)
|
||||
{
|
||||
case Log::INFO:
|
||||
stream << "(I) ";
|
||||
break;
|
||||
|
@ -140,12 +144,14 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
|
|||
|
||||
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();
|
||||
int counter = 0;
|
||||
QString backupLogFilename = m_path + ".bak";
|
||||
|
||||
while (QFile::exists(backupLogFilename)) {
|
||||
while (QFile::exists(backupLogFilename))
|
||||
{
|
||||
++counter;
|
||||
backupLogFilename = m_path + ".bak" + QString::number(counter);
|
||||
}
|
||||
|
@ -153,7 +159,8 @@ void FileLogger::addLogMessage(const Log::Msg &msg)
|
|||
QFile::rename(m_path, backupLogFilename);
|
||||
openLogFile();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (!m_flusher.isActive())
|
||||
m_flusher.start();
|
||||
}
|
||||
|
@ -168,7 +175,8 @@ void FileLogger::flushLog()
|
|||
void FileLogger::openLogFile()
|
||||
{
|
||||
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();
|
||||
LogMsg(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,8 @@ void sigNormalHandler(int signum);
|
|||
void sigAbnormalHandler(int signum);
|
||||
#endif
|
||||
// sys_signame[] is only defined in BSD
|
||||
const char *const sysSigName[] = {
|
||||
const char *const sysSigName[] =
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
"", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
|
||||
"", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
|
||||
|
@ -141,19 +142,23 @@ int main(int argc, char *argv[])
|
|||
Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
#endif
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
// Create Application
|
||||
auto app = std::make_unique<Application>(argc, argv);
|
||||
|
||||
const QBtCommandLineParameters params = app->commandLineArgs();
|
||||
if (!params.unknownParameter.isEmpty()) {
|
||||
if (!params.unknownParameter.isEmpty())
|
||||
{
|
||||
throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
|
||||
"--random-parameter is an unknown command line parameter.")
|
||||
.arg(params.unknownParameter));
|
||||
}
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
if (params.showVersion) {
|
||||
if (isOneArg) {
|
||||
if (params.showVersion)
|
||||
{
|
||||
if (isOneArg)
|
||||
{
|
||||
displayVersion();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -161,8 +166,10 @@ int main(int argc, char *argv[])
|
|||
.arg(QLatin1String("-v (or --version)")));
|
||||
}
|
||||
#endif
|
||||
if (params.showHelp) {
|
||||
if (isOneArg) {
|
||||
if (params.showHelp)
|
||||
{
|
||||
if (isOneArg)
|
||||
{
|
||||
displayUsage(argv[0]);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
@ -175,7 +182,8 @@ int main(int argc, char *argv[])
|
|||
fprintf(stderr, "Couldn't set environment variable...\n");
|
||||
|
||||
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
|
||||
if (firstTimeUser) {
|
||||
if (firstTimeUser)
|
||||
{
|
||||
#ifndef DISABLE_GUI
|
||||
if (!userAgreesWithLegalNotice())
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -195,9 +203,11 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
// Check if qBittorrent is already running for this user
|
||||
if (app->isRunning()) {
|
||||
if (app->isRunning())
|
||||
{
|
||||
#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.")
|
||||
.arg(QLatin1String("-d (or --daemon)")));
|
||||
}
|
||||
|
@ -242,7 +252,8 @@ int main(int argc, char *argv[])
|
|||
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
|
||||
if (!firstTimeUser) {
|
||||
if (!firstTimeUser)
|
||||
{
|
||||
handleChangedDefaults(DefaultPreferencesMode::Legacy);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
|
@ -256,21 +267,26 @@ int main(int argc, char *argv[])
|
|||
&& isatty(fileno(stdout)))) return EXIT_FAILURE;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
handleChangedDefaults(DefaultPreferencesMode::Current);
|
||||
}
|
||||
|
||||
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
|
||||
if (params.shouldDaemonize) {
|
||||
if (params.shouldDaemonize)
|
||||
{
|
||||
app.reset(); // Destroy current application
|
||||
if (daemon(1, 0) == 0) {
|
||||
if (daemon(1, 0) == 0)
|
||||
{
|
||||
app = std::make_unique<Application>(argc, argv);
|
||||
if (app->isRunning()) {
|
||||
if (app->isRunning())
|
||||
{
|
||||
// Another instance had time to start.
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qCritical("Something went wrong while daemonizing, exiting...");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
@ -289,7 +305,8 @@ int main(int argc, char *argv[])
|
|||
|
||||
return app->exec(params.paramList());
|
||||
}
|
||||
catch (const CommandLineParameterError &er) {
|
||||
catch (const CommandLineParameterError &er)
|
||||
{
|
||||
displayBadArgMessage(er.messageForUser());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
@ -300,10 +317,12 @@ void reportToUser(const char *str)
|
|||
{
|
||||
const size_t strLen = strlen(str);
|
||||
#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);
|
||||
#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);
|
||||
#endif
|
||||
Q_UNUSED(dummy);
|
||||
|
@ -405,7 +424,8 @@ bool userAgreesWithLegalNotice()
|
|||
printf("%s", qUtf8Printable(eula));
|
||||
|
||||
const char ret = getchar(); // Read pressed key
|
||||
if ((ret == 'y') || (ret == 'Y')) {
|
||||
if ((ret == 'y') || (ret == 'Y'))
|
||||
{
|
||||
// Save the answer
|
||||
pref->setAcceptedLegal(true);
|
||||
return true;
|
||||
|
@ -419,7 +439,8 @@ bool userAgreesWithLegalNotice()
|
|||
msgBox.show(); // Need to be shown or to moveToCenter does not work
|
||||
msgBox.move(Utils::Gui::screenCenter(&msgBox));
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == agreeButton) {
|
||||
if (msgBox.clickedButton() == agreeButton)
|
||||
{
|
||||
// Save the answer
|
||||
pref->setAcceptedLegal(true);
|
||||
return true;
|
||||
|
|
|
@ -100,7 +100,8 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
|||
, id(appId)
|
||||
{
|
||||
QString prefix = id;
|
||||
if (id.isEmpty()) {
|
||||
if (id.isEmpty())
|
||||
{
|
||||
id = QCoreApplication::applicationFilePath();
|
||||
#if defined(Q_OS_WIN)
|
||||
id = id.toLower();
|
||||
|
@ -143,7 +144,8 @@ bool QtLocalPeer::isClient()
|
|||
bool res = server->listen(socketName);
|
||||
#if defined(Q_OS_UNIX)
|
||||
// ### Workaround
|
||||
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
|
||||
if (!res && server->serverError() == QAbstractSocket::AddressInUseError)
|
||||
{
|
||||
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
|
||||
res = server->listen(socketName);
|
||||
}
|
||||
|
@ -161,7 +163,8 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
|
|||
|
||||
QLocalSocket socket;
|
||||
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
|
||||
socket.connectToServer(socketName);
|
||||
connOk = socket.waitForConnected(timeout/2);
|
||||
|
@ -182,7 +185,8 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
|
|||
QDataStream ds(&socket);
|
||||
ds.writeBytes(uMsg.constData(), uMsg.size());
|
||||
bool res = socket.waitForBytesWritten(timeout);
|
||||
if (res) {
|
||||
if (res)
|
||||
{
|
||||
res &= socket.waitForReadyRead(timeout); // wait for ack
|
||||
if (res)
|
||||
res &= (socket.read(qstrlen(ack)) == ack);
|
||||
|
@ -201,8 +205,10 @@ void QtLocalPeer::receiveConnection()
|
|||
if (!socket)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
if (socket->state() == QLocalSocket::UnconnectedState) {
|
||||
while (true)
|
||||
{
|
||||
if (socket->state() == QLocalSocket::UnconnectedState)
|
||||
{
|
||||
qWarning("QtLocalPeer: Peer disconnected");
|
||||
delete socket;
|
||||
return;
|
||||
|
@ -216,7 +222,8 @@ void QtLocalPeer::receiveConnection()
|
|||
QByteArray uMsg;
|
||||
quint32 remaining;
|
||||
ds >> remaining;
|
||||
if (remaining > 65535) {
|
||||
if (remaining > 65535)
|
||||
{
|
||||
// drop suspiciously large data
|
||||
delete socket;
|
||||
return;
|
||||
|
@ -225,12 +232,14 @@ void QtLocalPeer::receiveConnection()
|
|||
uMsg.resize(remaining);
|
||||
int got = 0;
|
||||
char* uMsgBuf = uMsg.data();
|
||||
do {
|
||||
do
|
||||
{
|
||||
got = ds.readRawData(uMsgBuf, remaining);
|
||||
remaining -= got;
|
||||
uMsgBuf += got;
|
||||
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
|
||||
if (got < 0) {
|
||||
if (got < 0)
|
||||
{
|
||||
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
|
||||
delete socket;
|
||||
return;
|
||||
|
|
|
@ -22,7 +22,8 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
|
|||
// retrieve current stack addresses
|
||||
int addrlen = backtrace(addrlist.data(), addrlist.size());
|
||||
|
||||
if (addrlen == 0) {
|
||||
if (addrlen == 0)
|
||||
{
|
||||
fprintf(out, " <empty, possibly corrupt>\n");
|
||||
return;
|
||||
}
|
||||
|
@ -38,27 +39,33 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
|
|||
int functionNamesFound = 0;
|
||||
// iterate over the returned symbol lines. skip the first, it is the
|
||||
// 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;
|
||||
|
||||
// find parentheses and +address offset surrounding the mangled name:
|
||||
// ./module(function+0x15c) [0x8048a6d]
|
||||
// fprintf(out, "%s TT\n", symbollist[i]);
|
||||
for (char *p = symbollist[i]; *p; ++p) {
|
||||
if (*p == '(') {
|
||||
for (char *p = symbollist[i]; *p; ++p)
|
||||
{
|
||||
if (*p == '(')
|
||||
{
|
||||
begin_name = p;
|
||||
}
|
||||
else if (*p == '+') {
|
||||
else if (*p == '+')
|
||||
{
|
||||
begin_offset = p;
|
||||
}
|
||||
else if ((*p == ')') && begin_offset) {
|
||||
else if ((*p == ')') && begin_offset)
|
||||
{
|
||||
end_offset = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (begin_name && begin_offset && end_offset
|
||||
&& (begin_name < begin_offset)) {
|
||||
&& (begin_name < begin_offset))
|
||||
{
|
||||
*begin_name++ = '\0';
|
||||
*begin_offset++ = '\0';
|
||||
*end_offset = '\0';
|
||||
|
@ -70,12 +77,14 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
|
|||
int status;
|
||||
char *ret = abi::__cxa_demangle(begin_name,
|
||||
funcname, &funcnamesize, &status);
|
||||
if (status == 0) {
|
||||
if (status == 0)
|
||||
{
|
||||
funcname = ret; // use possibly realloc()-ed string
|
||||
fprintf(out, " %s : %s+%s %s\n",
|
||||
symbollist[i], funcname, begin_offset, ++end_offset);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// demangling failed. Output function name as a C function with
|
||||
// no arguments.
|
||||
fprintf(out, " %s : %s()+%s %s\n",
|
||||
|
@ -83,17 +92,20 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
|
|||
}
|
||||
++functionNamesFound;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// couldn't parse the line? print the whole line.
|
||||
fprintf(out, " %s\n", symbollist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!functionNamesFound) {
|
||||
if (!functionNamesFound)
|
||||
{
|
||||
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");
|
||||
}
|
||||
if (functionNamesFound < addrlen - 2) {
|
||||
if (functionNamesFound < addrlen - 2)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
|
|
@ -54,7 +54,8 @@ void straceWin::demangle(QString& str)
|
|||
int status = 0;
|
||||
size_t outSz = 0;
|
||||
char* demangled_name = abi::__cxa_demangle(inStr, 0, &outSz, &status);
|
||||
if (status == 0) {
|
||||
if (status == 0)
|
||||
{
|
||||
str = QString::fromLocal8Bit(demangled_name);
|
||||
if (outSz > 0)
|
||||
free(demangled_name);
|
||||
|
@ -92,7 +93,8 @@ BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVO
|
|||
IMAGEHLP_MODULE64 mod;
|
||||
EnumModulesContext* context = (EnumModulesContext*)UserContext;
|
||||
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 line = QString::fromLatin1("%1 %2 Image: %3")
|
||||
.arg(mod.ModuleName, -25)
|
||||
|
@ -101,7 +103,8 @@ BOOL CALLBACK straceWin::EnumModulesCB(LPCSTR ModuleName, DWORD64 BaseOfDll, PVO
|
|||
context->stream << line << '\n';
|
||||
|
||||
QString pdbName(mod.LoadedPdbName);
|
||||
if(!pdbName.isEmpty()) {
|
||||
if(!pdbName.isEmpty())
|
||||
{
|
||||
QString line2 = QString::fromLatin1("%1 %2")
|
||||
.arg("", 35)
|
||||
.arg(pdbName);
|
||||
|
@ -126,7 +129,8 @@ bool straceWin::makeRelativePath(const QString& dir, QString& file)
|
|||
if (!d.isEmpty() && (d[d.length() - 1] != separator))
|
||||
d += separator;
|
||||
|
||||
if (f.startsWith(d, Qt::CaseInsensitive)) {
|
||||
if (f.startsWith(d, Qt::CaseInsensitive))
|
||||
{
|
||||
f.remove(0, d.length());
|
||||
file.swap(f);
|
||||
|
||||
|
@ -142,7 +146,8 @@ QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
|||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
DWORD dwDisplacement = 0;
|
||||
|
||||
if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line)) {
|
||||
if (SymGetLineFromAddr64(hProcess, addr, &dwDisplacement, &line))
|
||||
{
|
||||
QString path(line.FileName);
|
||||
|
||||
#if defined STACKTRACE_WIN_PROJECT_PATH || defined STACKTRACE_WIN_MAKEFILE_PATH
|
||||
|
@ -159,7 +164,8 @@ QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
|||
#endif
|
||||
|
||||
#ifdef STACKTRACE_WIN_MAKEFILE_PATH
|
||||
if (!success) {
|
||||
if (!success)
|
||||
{
|
||||
QString targetPath(STACKTRACE_WIN_STRING(STACKTRACE_WIN_MAKEFILE_PATH));
|
||||
makeRelativePath(targetPath, path);
|
||||
}
|
||||
|
@ -201,7 +207,8 @@ const QString straceWin::getBacktrace()
|
|||
: //no input
|
||||
: "eax");
|
||||
#else
|
||||
_asm {
|
||||
_asm
|
||||
{
|
||||
Label:
|
||||
mov [Context.Ebp], ebp;
|
||||
mov [Context.Esp], esp;
|
||||
|
@ -269,15 +276,18 @@ const QString straceWin::getBacktrace()
|
|||
|
||||
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)
|
||||
break;
|
||||
|
||||
loadHelpStackFrame(ihsf, StackFrame);
|
||||
if(StackFrame.AddrPC.Offset != 0) { // Valid frame.
|
||||
if(StackFrame.AddrPC.Offset != 0)
|
||||
{ // Valid frame.
|
||||
|
||||
QString fileName("???");
|
||||
if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod)) {
|
||||
if(SymGetModuleInfo64(hProcess, ihsf.InstructionOffset, &mod))
|
||||
{
|
||||
fileName = QString(mod.ImageName);
|
||||
int slashPos = fileName.lastIndexOf('\\');
|
||||
if(slashPos != -1)
|
||||
|
@ -285,7 +295,8 @@ const QString straceWin::getBacktrace()
|
|||
}
|
||||
QString funcName;
|
||||
QString sourceFile;
|
||||
if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol)) {
|
||||
if(SymFromAddr(hProcess, ihsf.InstructionOffset, &dwDisplacement, pSymbol))
|
||||
{
|
||||
funcName = QString(pSymbol->Name);
|
||||
#ifdef __MINGW32__
|
||||
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
|
||||
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
funcName = QString::fromLatin1("0x%1").arg(ihsf.InstructionOffset, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
SymSetContext(hProcess, &ihsf, NULL);
|
||||
|
@ -325,7 +337,8 @@ const QString straceWin::getBacktrace()
|
|||
logStream << debugLine << '\n';
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
break; // we're at the end.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ namespace
|
|||
{
|
||||
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()};
|
||||
const QByteArray oldData {settingsStorage->loadValue(oldKey).toByteArray()};
|
||||
const QString newData {settingsStorage->loadValue(newKey).toString()};
|
||||
|
@ -49,11 +50,13 @@ namespace
|
|||
return;
|
||||
|
||||
QFile file(savePath);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
LogMsg(errorMsgFormat.arg(savePath, file.errorString()) , Log::WARNING);
|
||||
return;
|
||||
}
|
||||
if (file.write(oldData) != oldData.size()) {
|
||||
if (file.write(oldData) != oldData.size())
|
||||
{
|
||||
file.close();
|
||||
Utils::Fs::forceRemove(savePath);
|
||||
LogMsg(errorMsgFormat.arg(savePath, QLatin1String("Write incomplete.")) , Log::WARNING);
|
||||
|
@ -92,12 +95,14 @@ void handleChangedDefaults(const DefaultPreferencesMode mode)
|
|||
QVariant current;
|
||||
};
|
||||
|
||||
const QVector<DefaultValue> changedDefaults {
|
||||
const QVector<DefaultValue> changedDefaults
|
||||
{
|
||||
{QLatin1String {"BitTorrent/Session/QueueingSystemEnabled"}, true, false}
|
||||
};
|
||||
|
||||
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())
|
||||
settingsStorage->storeValue(it->name, (mode == DefaultPreferencesMode::Legacy ? it->legacy : it->current));
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *pa
|
|||
, m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock")))
|
||||
{
|
||||
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())};
|
||||
|
||||
// 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);
|
||||
QSaveFile file(filePath);
|
||||
qDebug() << "AsyncFileStorage: Saving data to" << filePath;
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
if (file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
file.write(data);
|
||||
if (!file.commit()) {
|
||||
if (!file.commit())
|
||||
{
|
||||
qDebug() << "AsyncFileStorage: Failed to save data";
|
||||
emit failed(filePath, file.errorString());
|
||||
}
|
||||
|
|
|
@ -65,13 +65,16 @@ bool BandwidthScheduler::isTimeForAlternative() const
|
|||
const int day = QDate::currentDate().dayOfWeek();
|
||||
bool alternative = false;
|
||||
|
||||
if (start > end) {
|
||||
if (start > end)
|
||||
{
|
||||
std::swap(start, end);
|
||||
alternative = true;
|
||||
}
|
||||
|
||||
if ((start <= now) && (end >= now)) {
|
||||
switch (schedulerDays) {
|
||||
if ((start <= now) && (end >= now))
|
||||
{
|
||||
switch (schedulerDays)
|
||||
{
|
||||
case EVERY_DAY:
|
||||
alternative = !alternative;
|
||||
break;
|
||||
|
@ -96,7 +99,8 @@ void BandwidthScheduler::onTimeout()
|
|||
{
|
||||
const bool alternative = isTimeForAlternative();
|
||||
|
||||
if (alternative != m_lastAlternative) {
|
||||
if (alternative != m_lastAlternative)
|
||||
{
|
||||
m_lastAlternative = alternative;
|
||||
emit bandwidthLimitRequested(alternative);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
const QString savePath = Utils::Fs::expandPathAbs(QString::fromStdString(storageParams.path));
|
||||
m_storageData[storageHolder] = {
|
||||
m_storageData[storageHolder] =
|
||||
{
|
||||
savePath
|
||||
, storageParams.mapped_files ? *storageParams.mapped_files : storageParams.files
|
||||
, storageParams.priorities};
|
||||
|
@ -196,7 +197,8 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
|
|||
const QDir saveDir {savePath};
|
||||
const StorageData storageData = m_storageData[storage];
|
||||
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
|
||||
if ((storageData.filePriorities.end_index() > fileIndex) && (storageData.filePriorities[fileIndex] == lt::dont_download))
|
||||
continue;
|
||||
|
@ -205,10 +207,12 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
|
|||
if (fileStorage.pad_file_at(fileIndex)) continue;
|
||||
|
||||
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());
|
||||
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
|
||||
if (completeFile.exists()) {
|
||||
if (completeFile.exists())
|
||||
{
|
||||
QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
|
||||
incompleteFile.remove();
|
||||
completeFile.rename(incompleteFile.fileName());
|
||||
|
@ -261,7 +265,8 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
|
|||
const QDir saveDir {savePath};
|
||||
|
||||
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
|
||||
if ((m_filePriorities.end_index() > fileIndex) && (m_filePriorities[fileIndex] == lt::dont_download))
|
||||
continue;
|
||||
|
@ -270,10 +275,12 @@ void CustomStorage::handleCompleteFiles(const QString &savePath)
|
|||
if (fileStorage.pad_file_at(fileIndex)) continue;
|
||||
|
||||
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());
|
||||
QFile completeFile {saveDir.absoluteFilePath(completeFilePath)};
|
||||
if (completeFile.exists()) {
|
||||
if (completeFile.exists())
|
||||
{
|
||||
QFile incompleteFile {saveDir.absoluteFilePath(filePath)};
|
||||
incompleteFile.remove();
|
||||
completeFile.rename(incompleteFile.fileName());
|
||||
|
|
|
@ -32,7 +32,8 @@ namespace BitTorrent
|
|||
{
|
||||
bool isValidDownloadPriority(const DownloadPriority priority)
|
||||
{
|
||||
switch (priority) {
|
||||
switch (priority)
|
||||
{
|
||||
case DownloadPriority::Ignored:
|
||||
case DownloadPriority::Normal:
|
||||
case DownloadPriority::High:
|
||||
|
|
|
@ -48,8 +48,10 @@ namespace
|
|||
|
||||
const char *octetStart = str;
|
||||
char *endptr;
|
||||
for (; *str; ++str) {
|
||||
if (*str == '.') {
|
||||
for (; *str; ++str)
|
||||
{
|
||||
if (*str == '.')
|
||||
{
|
||||
const long int extractedNum = strtol(octetStart, &endptr, 10);
|
||||
if ((extractedNum >= 0L) && (extractedNum <= 255L))
|
||||
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);
|
||||
if ((extractedNum >= 0L) && (extractedNum <= 255L))
|
||||
m_buf[octetIndex] = static_cast<unsigned char>(strtol(octetStart, &endptr, 10));
|
||||
|
@ -124,7 +127,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
QFile file(m_filePath);
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -142,7 +146,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
LogMsg(msg, Log::CRITICAL);
|
||||
};
|
||||
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
|
@ -150,12 +155,16 @@ int FilterParserThread::parseDATFilterFile()
|
|||
if ((bytesRead == 0) && (dataSize == 0))
|
||||
break;
|
||||
|
||||
for (start = 0; start < dataSize; ++start) {
|
||||
for (start = 0; start < dataSize; ++start)
|
||||
{
|
||||
endOfLine = -1;
|
||||
// The file might have ended without the last line having a newline
|
||||
if (!((bytesRead == 0) && (dataSize > 0))) {
|
||||
for (int i = start; i < dataSize; ++i) {
|
||||
if (buffer[i] == '\n') {
|
||||
if (!((bytesRead == 0) && (dataSize > 0)))
|
||||
{
|
||||
for (int i = start; i < dataSize; ++i)
|
||||
{
|
||||
if (buffer[i] == '\n')
|
||||
{
|
||||
endOfLine = i;
|
||||
// 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
|
||||
|
@ -165,12 +174,14 @@ int FilterParserThread::parseDATFilterFile()
|
|||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
endOfLine = dataSize;
|
||||
buffer[dataSize] = '\0';
|
||||
}
|
||||
|
||||
if (endOfLine == -1) {
|
||||
if (endOfLine == -1)
|
||||
{
|
||||
// read the next chunk from file
|
||||
// but first move(copy) the leftover data to the front of the buffer
|
||||
offset = dataSize - start;
|
||||
|
@ -181,7 +192,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
++nbLine;
|
||||
|
||||
if ((buffer[start] == '#')
|
||||
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/')))) {
|
||||
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
|
||||
{
|
||||
start = endOfLine;
|
||||
continue;
|
||||
}
|
||||
|
@ -194,11 +206,13 @@ int FilterParserThread::parseDATFilterFile()
|
|||
findAndNullDelimiter(buffer.data(), ',', firstComma + 1, endOfLine);
|
||||
|
||||
// Check if there is an access value (apparently not mandatory)
|
||||
if (firstComma != -1) {
|
||||
if (firstComma != -1)
|
||||
{
|
||||
// There is possibly one
|
||||
const long int nbAccess = strtol(buffer.data() + firstComma + 1, nullptr, 10);
|
||||
// Ignoring this rule because access value is too high
|
||||
if (nbAccess > 127L) {
|
||||
if (nbAccess > 127L)
|
||||
{
|
||||
start = endOfLine;
|
||||
continue;
|
||||
}
|
||||
|
@ -207,7 +221,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
// IP Range should be split by a dash
|
||||
const int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
|
||||
const int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
|
||||
if (delimIP == -1) {
|
||||
if (delimIP == -1)
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -216,7 +231,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
|
||||
lt::address startAddr;
|
||||
int newStart = trim(buffer.data(), start, delimIP - 1);
|
||||
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
|
||||
if (!parseIPAddress(buffer.data() + newStart, startAddr))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -225,7 +241,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
|
||||
lt::address endAddr;
|
||||
newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
|
||||
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
|
||||
if (!parseIPAddress(buffer.data() + newStart, endAddr))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -233,7 +250,8 @@ int FilterParserThread::parseDATFilterFile()
|
|||
}
|
||||
|
||||
if ((startAddr.is_v4() != endAddr.is_v4())
|
||||
|| (startAddr.is_v6() != endAddr.is_v6())) {
|
||||
|| (startAddr.is_v6() != endAddr.is_v6()))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -243,11 +261,13 @@ int FilterParserThread::parseDATFilterFile()
|
|||
start = endOfLine;
|
||||
|
||||
// Now Add to the filter
|
||||
try {
|
||||
try
|
||||
{
|
||||
m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
|
||||
.arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
|
||||
|
@ -271,7 +291,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
QFile file(m_filePath);
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -289,7 +310,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
LogMsg(msg, Log::CRITICAL);
|
||||
};
|
||||
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
|
@ -297,12 +319,16 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
if ((bytesRead == 0) && (dataSize == 0))
|
||||
break;
|
||||
|
||||
for (start = 0; start < dataSize; ++start) {
|
||||
for (start = 0; start < dataSize; ++start)
|
||||
{
|
||||
endOfLine = -1;
|
||||
// The file might have ended without the last line having a newline
|
||||
if (!((bytesRead == 0) && (dataSize > 0))) {
|
||||
for (int i = start; i < dataSize; ++i) {
|
||||
if (buffer[i] == '\n') {
|
||||
if (!((bytesRead == 0) && (dataSize > 0)))
|
||||
{
|
||||
for (int i = start; i < dataSize; ++i)
|
||||
{
|
||||
if (buffer[i] == '\n')
|
||||
{
|
||||
endOfLine = i;
|
||||
// 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
|
||||
|
@ -312,12 +338,14 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
endOfLine = dataSize;
|
||||
buffer[dataSize] = '\0';
|
||||
}
|
||||
|
||||
if (endOfLine == -1) {
|
||||
if (endOfLine == -1)
|
||||
{
|
||||
// read the next chunk from file
|
||||
// but first move(copy) the leftover data to the front of the buffer
|
||||
offset = dataSize - start;
|
||||
|
@ -328,7 +356,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
++nbLine;
|
||||
|
||||
if ((buffer[start] == '#')
|
||||
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/')))) {
|
||||
|| ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
|
||||
{
|
||||
start = endOfLine;
|
||||
continue;
|
||||
}
|
||||
|
@ -337,7 +366,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
// 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
|
||||
const int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
|
||||
if (partsDelimiter == -1) {
|
||||
if (partsDelimiter == -1)
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -346,7 +376,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
|
||||
// IP Range should be split by a dash
|
||||
const int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
|
||||
if (delimIP == -1) {
|
||||
if (delimIP == -1)
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -355,7 +386,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
|
||||
lt::address startAddr;
|
||||
int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
|
||||
if (!parseIPAddress(buffer.data() + newStart, startAddr)) {
|
||||
if (!parseIPAddress(buffer.data() + newStart, startAddr))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -364,7 +396,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
|
||||
lt::address endAddr;
|
||||
newStart = trim(buffer.data(), delimIP + 1, endOfLine);
|
||||
if (!parseIPAddress(buffer.data() + newStart, endAddr)) {
|
||||
if (!parseIPAddress(buffer.data() + newStart, endAddr))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -372,7 +405,8 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
}
|
||||
|
||||
if ((startAddr.is_v4() != endAddr.is_v4())
|
||||
|| (startAddr.is_v6() != endAddr.is_v6())) {
|
||||
|| (startAddr.is_v6() != endAddr.is_v6()))
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
|
||||
start = endOfLine;
|
||||
|
@ -381,11 +415,13 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
|
||||
start = endOfLine;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
++parseErrorCount;
|
||||
addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
|
||||
.arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
|
||||
|
@ -407,14 +443,18 @@ int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name,
|
|||
char c;
|
||||
int totalRead = 0;
|
||||
int read;
|
||||
do {
|
||||
do
|
||||
{
|
||||
read = stream.readRawData(&c, 1);
|
||||
totalRead += read;
|
||||
if (read > 0) {
|
||||
if (c != delim) {
|
||||
if (read > 0)
|
||||
{
|
||||
if (c != delim)
|
||||
{
|
||||
name += c;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Delim found
|
||||
return totalRead;
|
||||
}
|
||||
|
@ -432,7 +472,8 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
QFile file(m_filePath);
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -443,19 +484,23 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
unsigned char version;
|
||||
if (!stream.readRawData(buf, sizeof(buf))
|
||||
|| 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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
if ((version == 1) || (version == 2)) {
|
||||
if ((version == 1) || (version == 2))
|
||||
{
|
||||
qDebug ("p2b version 1 or 2");
|
||||
unsigned int start, end;
|
||||
|
||||
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))
|
||||
|| !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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -466,26 +511,31 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
const lt::address_v4 first(ntohl(start));
|
||||
const lt::address_v4 last(ntohl(end));
|
||||
// Apply to bittorrent session
|
||||
try {
|
||||
try
|
||||
{
|
||||
m_filter.add_rule(first, last, lt::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
}
|
||||
}
|
||||
else if (version == 3) {
|
||||
else if (version == 3)
|
||||
{
|
||||
qDebug ("p2b version 3");
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
namecount = ntohl(namecount);
|
||||
// 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;
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -495,17 +545,20 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
|
||||
// Reading the ranges
|
||||
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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
||||
rangecount = ntohl(rangecount);
|
||||
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))
|
||||
|| !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);
|
||||
return ruleCount;
|
||||
}
|
||||
|
@ -516,7 +569,8 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
const lt::address_v4 first(ntohl(start));
|
||||
const lt::address_v4 last(ntohl(end));
|
||||
// Apply to bittorrent session
|
||||
try {
|
||||
try
|
||||
{
|
||||
m_filter.add_rule(first, last, lt::ip_filter::blocked);
|
||||
++ruleCount;
|
||||
}
|
||||
|
@ -525,7 +579,8 @@ int FilterParserThread::parseP2BFilterFile()
|
|||
if (m_abort) return ruleCount;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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
|
||||
void FilterParserThread::processFilterFile(const QString &filePath)
|
||||
{
|
||||
if (isRunning()) {
|
||||
if (isRunning())
|
||||
{
|
||||
// Already parsing a filter, m_abort first
|
||||
m_abort = true;
|
||||
wait();
|
||||
|
@ -561,25 +617,30 @@ void FilterParserThread::run()
|
|||
{
|
||||
qDebug("Processing filter file");
|
||||
int ruleCount = 0;
|
||||
if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive)) {
|
||||
if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive))
|
||||
{
|
||||
// PeerGuardian p2p file
|
||||
ruleCount = parseP2PFilterFile();
|
||||
}
|
||||
else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive)) {
|
||||
else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive))
|
||||
{
|
||||
// PeerGuardian p2b file
|
||||
ruleCount = parseP2BFilterFile();
|
||||
}
|
||||
else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive)) {
|
||||
else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive))
|
||||
{
|
||||
// eMule DAT format
|
||||
ruleCount = parseDATFilterFile();
|
||||
}
|
||||
|
||||
if (m_abort) return;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
emit IPFilterParsed(ruleCount);
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
catch (const std::exception &)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!reverse) {
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (data[i] == delimiter) {
|
||||
if (!reverse)
|
||||
{
|
||||
for (int i = start; i <= end; ++i)
|
||||
{
|
||||
if (data[i] == delimiter)
|
||||
{
|
||||
data[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = end; i >= start; --i) {
|
||||
if (data[i] == delimiter) {
|
||||
else
|
||||
{
|
||||
for (int i = end; i >= start; --i)
|
||||
{
|
||||
if (data[i] == delimiter)
|
||||
{
|
||||
data[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
@ -613,17 +680,21 @@ int FilterParserThread::trim(char *const data, const int start, const int end)
|
|||
if (start >= end) return start;
|
||||
int newStart = start;
|
||||
|
||||
for (int i = start; i <= end; ++i) {
|
||||
if (isspace(data[i]) != 0) {
|
||||
for (int i = start; i <= end; ++i)
|
||||
{
|
||||
if (isspace(data[i]) != 0)
|
||||
{
|
||||
data[i] = '\0';
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
newStart = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = end; i >= start; --i) {
|
||||
for (int i = end; i >= start; --i)
|
||||
{
|
||||
if (isspace(data[i]) != 0)
|
||||
data[i] = '\0';
|
||||
else
|
||||
|
|
|
@ -36,7 +36,8 @@ namespace
|
|||
{
|
||||
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.pause();
|
||||
}
|
||||
|
@ -55,7 +56,8 @@ std::shared_ptr<lt::torrent_plugin> NativeSessionExtension::new_torrent(const lt
|
|||
|
||||
void NativeSessionExtension::on_alert(const lt::alert *alert)
|
||||
{
|
||||
switch (alert->type()) {
|
||||
switch (alert->type())
|
||||
{
|
||||
case lt::fastresume_rejected_alert::alert_type:
|
||||
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
|
||||
break;
|
||||
|
|
|
@ -36,14 +36,17 @@ PeerAddress PeerAddress::parse(const QString &address)
|
|||
{
|
||||
QVector<QStringRef> ipPort;
|
||||
|
||||
if (address.startsWith('[') && address.contains("]:")) { // IPv6
|
||||
if (address.startsWith('[') && address.contains("]:"))
|
||||
{ // IPv6
|
||||
ipPort = address.splitRef("]:");
|
||||
ipPort[0] = ipPort[0].mid(1); // chop '['
|
||||
}
|
||||
else if (address.contains(':')) { // IPv4
|
||||
else if (address.contains(':'))
|
||||
{ // IPv4
|
||||
ipPort = address.splitRef(':');
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -208,7 +208,8 @@ qlonglong PeerInfo::totalDownload() const
|
|||
QBitArray PeerInfo::pieces() const
|
||||
{
|
||||
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}])
|
||||
result.setBit(i, true);
|
||||
}
|
||||
|
@ -233,8 +234,10 @@ void PeerInfo::calcRelevance(const TorrentHandle *torrent)
|
|||
int localMissing = 0;
|
||||
int remoteHaves = 0;
|
||||
|
||||
for (int i = 0; i < allPieces.size(); ++i) {
|
||||
if (!allPieces[i]) {
|
||||
for (int i = 0; i < allPieces.size(); ++i)
|
||||
{
|
||||
if (!allPieces[i])
|
||||
{
|
||||
++localMissing;
|
||||
if (peerPieces[i])
|
||||
++remoteHaves;
|
||||
|
@ -254,14 +257,17 @@ qreal PeerInfo::relevance() const
|
|||
|
||||
void PeerInfo::determineFlags()
|
||||
{
|
||||
if (isInteresting()) {
|
||||
if (isInteresting())
|
||||
{
|
||||
// d = Your client wants to download, but peer doesn't want to send (interested and choked)
|
||||
if (isRemoteChocked()) {
|
||||
if (isRemoteChocked())
|
||||
{
|
||||
m_flags += "d ";
|
||||
m_flagsDescription += ("d = "
|
||||
+ tr("Interested(local) and Choked(peer)") + '\n');
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// D = Currently downloading (interested and not choked)
|
||||
m_flags += "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)
|
||||
if (isChocked()) {
|
||||
if (isChocked())
|
||||
{
|
||||
m_flags += "u ";
|
||||
m_flagsDescription += ("u = "
|
||||
+ tr("interested(peer) and choked(local)") + '\n');
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// U = Currently uploading (interested and not choked)
|
||||
m_flags += "U ";
|
||||
m_flagsDescription += ("U = "
|
||||
|
@ -285,69 +294,80 @@ void PeerInfo::determineFlags()
|
|||
}
|
||||
|
||||
// O = Optimistic unchoke
|
||||
if (optimisticUnchoke()) {
|
||||
if (optimisticUnchoke())
|
||||
{
|
||||
m_flags += "O ";
|
||||
m_flagsDescription += ("O = " + tr("optimistic unchoke") + '\n');
|
||||
}
|
||||
|
||||
// S = Peer is snubbed
|
||||
if (isSnubbed()) {
|
||||
if (isSnubbed())
|
||||
{
|
||||
m_flags += "S ";
|
||||
m_flagsDescription += ("S = " + tr("peer snubbed") + '\n');
|
||||
}
|
||||
|
||||
// I = Peer is an incoming connection
|
||||
if (!isLocalConnection()) {
|
||||
if (!isLocalConnection())
|
||||
{
|
||||
m_flags += "I ";
|
||||
m_flagsDescription += ("I = " + tr("incoming connection") + '\n');
|
||||
}
|
||||
|
||||
// K = Peer is unchoking your client, but your client is not interested
|
||||
if (!isRemoteChocked() && !isInteresting()) {
|
||||
if (!isRemoteChocked() && !isInteresting())
|
||||
{
|
||||
m_flags += "K ";
|
||||
m_flagsDescription += ("K = "
|
||||
+ tr("not interested(local) and unchoked(peer)") + '\n');
|
||||
}
|
||||
|
||||
// ? = Your client unchoked the peer but the peer is not interested
|
||||
if (!isChocked() && !isRemoteInterested()) {
|
||||
if (!isChocked() && !isRemoteInterested())
|
||||
{
|
||||
m_flags += "? ";
|
||||
m_flagsDescription += ("? = "
|
||||
+ tr("not interested(peer) and unchoked(local)") + '\n');
|
||||
}
|
||||
|
||||
// X = Peer was included in peerlists obtained through Peer Exchange (PEX)
|
||||
if (fromPeX()) {
|
||||
if (fromPeX())
|
||||
{
|
||||
m_flags += "X ";
|
||||
m_flagsDescription += ("X = " + tr("peer from PEX") + '\n');
|
||||
}
|
||||
|
||||
// H = Peer was obtained through DHT
|
||||
if (fromDHT()) {
|
||||
if (fromDHT())
|
||||
{
|
||||
m_flags += "H ";
|
||||
m_flagsDescription += ("H = " + tr("peer from DHT") + '\n');
|
||||
}
|
||||
|
||||
// E = Peer is using Protocol Encryption (all traffic)
|
||||
if (isRC4Encrypted()) {
|
||||
if (isRC4Encrypted())
|
||||
{
|
||||
m_flags += "E ";
|
||||
m_flagsDescription += ("E = " + tr("encrypted traffic") + '\n');
|
||||
}
|
||||
|
||||
// e = Peer is using Protocol Encryption (handshake)
|
||||
if (isPlaintextEncrypted()) {
|
||||
if (isPlaintextEncrypted())
|
||||
{
|
||||
m_flags += "e ";
|
||||
m_flagsDescription += ("e = " + tr("encrypted handshake") + '\n');
|
||||
}
|
||||
|
||||
// P = Peer is using uTorrent uTP
|
||||
if (useUTPSocket()) {
|
||||
if (useUTPSocket())
|
||||
{
|
||||
m_flags += "P ";
|
||||
m_flagsDescription += ("P = " + QString::fromUtf8(C_UTP) + '\n');
|
||||
}
|
||||
|
||||
// L = Peer is local
|
||||
if (fromLSD()) {
|
||||
if (fromLSD())
|
||||
{
|
||||
m_flags += "L ";
|
||||
m_flagsDescription += ("L = " + tr("peer from LSD") + '\n');
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ bool PortForwarderImpl::isEnabled() const
|
|||
|
||||
void PortForwarderImpl::setEnabled(const bool enabled)
|
||||
{
|
||||
if (m_active != enabled) {
|
||||
if (m_active != enabled)
|
||||
{
|
||||
if (enabled)
|
||||
start();
|
||||
else
|
||||
|
@ -71,7 +72,8 @@ void PortForwarderImpl::setEnabled(const bool enabled)
|
|||
|
||||
void PortForwarderImpl::addPort(const quint16 port)
|
||||
{
|
||||
if (!m_mappedPorts.contains(port)) {
|
||||
if (!m_mappedPorts.contains(port))
|
||||
{
|
||||
m_mappedPorts.insert(port, {});
|
||||
if (isEnabled())
|
||||
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)
|
||||
{
|
||||
if (m_mappedPorts.contains(port)) {
|
||||
if (isEnabled()) {
|
||||
if (m_mappedPorts.contains(port))
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
for (const lt::port_mapping_t &portMapping : m_mappedPorts[port])
|
||||
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_natpmp, true);
|
||||
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();
|
||||
i.value() = {m_provider->add_port_mapping(lt::session::tcp, i.key(), i.key())};
|
||||
}
|
||||
|
|
|
@ -48,7 +48,8 @@ void ResumeDataSavingManager::save(const QString &filename, const QByteArray &da
|
|||
const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
|
||||
|
||||
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")
|
||||
.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);
|
||||
|
||||
QSaveFile file {filepath};
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
LogMsg(tr("Couldn't save data to '%1'. Error: %2")
|
||||
.arg(filepath, file.errorString()), Log::CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
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")
|
||||
.arg(filepath, file.errorString()), Log::CRITICAL);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -36,7 +36,8 @@ SpeedMonitor::SpeedMonitor()
|
|||
|
||||
void SpeedMonitor::addSample(const SpeedSample &sample)
|
||||
{
|
||||
if (m_speedSamples.size() >= MAX_SAMPLES) {
|
||||
if (m_speedSamples.size() >= MAX_SAMPLES)
|
||||
{
|
||||
m_sum -= m_speedSamples.front();
|
||||
}
|
||||
|
||||
|
|
|
@ -71,11 +71,13 @@ quint64 Statistics::getAlltimeUL() const
|
|||
void Statistics::gather()
|
||||
{
|
||||
const SessionStatus &ss = m_session->status();
|
||||
if (ss.totalDownload > m_sessionDL) {
|
||||
if (ss.totalDownload > m_sessionDL)
|
||||
{
|
||||
m_sessionDL = ss.totalDownload;
|
||||
m_dirty = true;
|
||||
}
|
||||
if (ss.totalUpload > m_sessionUL) {
|
||||
if (ss.totalUpload > m_sessionUL)
|
||||
{
|
||||
m_sessionUL = ss.totalUpload;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ namespace
|
|||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
lt::create_flags_t toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat)
|
||||
{
|
||||
switch (torrentFormat) {
|
||||
switch (torrentFormat)
|
||||
{
|
||||
case BitTorrent::TorrentFormat::V1:
|
||||
return lt::create_torrent::v1_only;
|
||||
case BitTorrent::TorrentFormat::Hybrid:
|
||||
|
@ -102,20 +103,24 @@ void TorrentCreatorThread::run()
|
|||
|
||||
emit updateProgress(0);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
|
||||
|
||||
// Adding files to the torrent
|
||||
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);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// need to sort the file names by natural sort order
|
||||
QStringList dirs = {m_params.inputPath};
|
||||
|
||||
QDirIterator dirIter(m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories);
|
||||
while (dirIter.hasNext()) {
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
dirIter.next();
|
||||
dirs += dirIter.filePath();
|
||||
}
|
||||
|
@ -124,11 +129,13 @@ void TorrentCreatorThread::run()
|
|||
QStringList fileNames;
|
||||
QHash<QString, qint64> fileSizeMap;
|
||||
|
||||
for (const auto &dir : asConst(dirs)) {
|
||||
for (const auto &dir : asConst(dirs))
|
||||
{
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter(dir, QDir::Files);
|
||||
while (fileIter.hasNext()) {
|
||||
while (fileIter.hasNext())
|
||||
{
|
||||
fileIter.next();
|
||||
|
||||
const QString relFilePath = fileIter.filePath().mid(parentPath.length());
|
||||
|
@ -154,14 +161,16 @@ void TorrentCreatorThread::run()
|
|||
#endif
|
||||
|
||||
// Add url seeds
|
||||
for (QString seed : asConst(m_params.urlSeeds)) {
|
||||
for (QString seed : asConst(m_params.urlSeeds))
|
||||
{
|
||||
seed = seed.trimmed();
|
||||
if (!seed.isEmpty())
|
||||
newTorrent.add_url_seed(seed.toStdString());
|
||||
}
|
||||
|
||||
int tier = 0;
|
||||
for (const QString &tracker : asConst(m_params.trackers)) {
|
||||
for (const QString &tracker : asConst(m_params.trackers))
|
||||
{
|
||||
if (tracker.isEmpty())
|
||||
++tier;
|
||||
else
|
||||
|
@ -195,16 +204,20 @@ void TorrentCreatorThread::run()
|
|||
|
||||
// create the torrent
|
||||
QFile outfile {m_params.savePath};
|
||||
if (!outfile.open(QIODevice::WriteOnly)) {
|
||||
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||
if (!outfile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
throw RuntimeError
|
||||
{tr("Create new torrent file failed. Reason: %1")
|
||||
.arg(outfile.errorString())};
|
||||
}
|
||||
|
||||
if (isInterruptionRequested()) return;
|
||||
|
||||
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
|
||||
if (outfile.error() != QFileDevice::NoError) {
|
||||
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||
if (outfile.error() != QFileDevice::NoError)
|
||||
{
|
||||
throw RuntimeError
|
||||
{tr("Create new torrent file failed. Reason: %1")
|
||||
.arg(outfile.errorString())};
|
||||
}
|
||||
outfile.close();
|
||||
|
@ -212,7 +225,8 @@ void TorrentCreatorThread::run()
|
|||
emit updateProgress(100);
|
||||
emit creationSuccess(m_params.savePath, parentPath);
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
emit creationFailure(e.what());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,10 +126,12 @@ TorrentHandleImpl::TorrentHandleImpl(Session *session, const lt::torrent_handle
|
|||
updateStatus();
|
||||
m_hash = InfoHash(m_nativeStatus.info_hash);
|
||||
|
||||
if (hasMetadata()) {
|
||||
if (hasMetadata())
|
||||
{
|
||||
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
|
||||
|
||||
if (!params.restored) {
|
||||
if (!params.restored)
|
||||
{
|
||||
if (filesCount() == 1)
|
||||
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
|
||||
// == BEGIN UPGRADE CODE ==
|
||||
const QString spath = actualStorageLocation();
|
||||
for (int i = 0; i < filesCount(); ++i) {
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
{
|
||||
const QString filepath = filePath(i);
|
||||
// Move "unwanted" files back to their original folder
|
||||
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 newRelPath = Utils::Fs::branchPath(parentRelPath);
|
||||
if (newRelPath.isEmpty())
|
||||
|
@ -178,7 +182,8 @@ QString TorrentHandleImpl::name() const
|
|||
name = QString::fromStdString(m_nativeStatus.name);
|
||||
if (!name.isEmpty()) return name;
|
||||
|
||||
if (hasMetadata()) {
|
||||
if (hasMetadata())
|
||||
{
|
||||
name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name());
|
||||
if (!name.isEmpty()) return name;
|
||||
}
|
||||
|
@ -330,8 +335,10 @@ void TorrentHandleImpl::addTrackers(const QVector<TrackerEntry> &trackers)
|
|||
QVector<TrackerEntry> newTrackers;
|
||||
newTrackers.reserve(trackers.size());
|
||||
|
||||
for (const TrackerEntry &tracker : trackers) {
|
||||
if (!currentTrackers.contains(tracker)) {
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
{
|
||||
if (!currentTrackers.contains(tracker))
|
||||
{
|
||||
m_nativeHandle.add_tracker(tracker.nativeEntry());
|
||||
newTrackers << tracker;
|
||||
}
|
||||
|
@ -351,7 +358,8 @@ void TorrentHandleImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
|||
std::vector<lt::announce_entry> nativeTrackers;
|
||||
nativeTrackers.reserve(trackers.size());
|
||||
|
||||
for (const TrackerEntry &tracker : trackers) {
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
{
|
||||
nativeTrackers.emplace_back(tracker.nativeEntry());
|
||||
|
||||
if (!currentTrackers.removeOne(tracker))
|
||||
|
@ -360,11 +368,13 @@ void TorrentHandleImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
|||
|
||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||
|
||||
if (newTrackers.isEmpty() && currentTrackers.isEmpty()) {
|
||||
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
|
||||
{
|
||||
// when existing tracker reorders
|
||||
m_session->handleTorrentTrackersChanged(this);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (!currentTrackers.isEmpty())
|
||||
m_session->handleTorrentTrackersRemoved(this, currentTrackers);
|
||||
|
||||
|
@ -398,9 +408,11 @@ void TorrentHandleImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||
QVector<QUrl> addedUrlSeeds;
|
||||
addedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds) {
|
||||
for (const QUrl &url : urlSeeds)
|
||||
{
|
||||
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);
|
||||
addedUrlSeeds << url;
|
||||
}
|
||||
|
@ -417,9 +429,11 @@ void TorrentHandleImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||
QVector<QUrl> removedUrlSeeds;
|
||||
removedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds) {
|
||||
for (const QUrl &url : urlSeeds)
|
||||
{
|
||||
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);
|
||||
removedUrlSeeds << url;
|
||||
}
|
||||
|
@ -443,10 +457,12 @@ bool TorrentHandleImpl::connectPeer(const PeerAddress &peerAddress)
|
|||
if (ec) return false;
|
||||
|
||||
const lt::tcp::endpoint endpoint(addr, peerAddress.port);
|
||||
try {
|
||||
try
|
||||
{
|
||||
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")
|
||||
.arg(peerAddress.toString(), name(), QString::fromLocal8Bit(err.what())), Log::WARNING);
|
||||
return false;
|
||||
|
@ -533,7 +549,8 @@ bool TorrentHandleImpl::addTag(const QString &tag)
|
|||
if (!Session::isValidTag(tag))
|
||||
return false;
|
||||
|
||||
if (!hasTag(tag)) {
|
||||
if (!hasTag(tag))
|
||||
{
|
||||
if (!m_session->hasTag(tag))
|
||||
if (!m_session->addTag(tag))
|
||||
return false;
|
||||
|
@ -546,7 +563,8 @@ bool TorrentHandleImpl::addTag(const QString &tag)
|
|||
|
||||
bool TorrentHandleImpl::removeTag(const QString &tag)
|
||||
{
|
||||
if (m_tags.remove(tag)) {
|
||||
if (m_tags.remove(tag))
|
||||
{
|
||||
m_session->handleTorrentTagRemoved(this, tag);
|
||||
return true;
|
||||
}
|
||||
|
@ -719,25 +737,31 @@ TorrentState TorrentHandleImpl::state() const
|
|||
|
||||
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;
|
||||
}
|
||||
else if (isMoveInProgress()) {
|
||||
else if (isMoveInProgress())
|
||||
{
|
||||
m_state = TorrentState::Moving;
|
||||
}
|
||||
else if (hasMissingFiles()) {
|
||||
else if (hasMissingFiles())
|
||||
{
|
||||
m_state = TorrentState::MissingFiles;
|
||||
}
|
||||
else if (hasError()) {
|
||||
else if (hasError())
|
||||
{
|
||||
m_state = TorrentState::Error;
|
||||
}
|
||||
else if ((m_nativeStatus.state == lt::torrent_status::checking_files)
|
||||
&& (!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
|
||||
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
|
||||
}
|
||||
else if (isSeed()) {
|
||||
else if (isSeed())
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedUploading;
|
||||
else if (m_session->isQueueingSystemEnabled() && isQueued())
|
||||
|
@ -749,7 +773,8 @@ void TorrentHandleImpl::updateState()
|
|||
else
|
||||
m_state = TorrentState::StalledUploading;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedDownloading;
|
||||
else if (m_nativeStatus.state == lt::torrent_status::downloading_metadata)
|
||||
|
@ -832,14 +857,16 @@ qlonglong TorrentHandleImpl::eta() const
|
|||
|
||||
const SpeedSampleAvg speedAverage = m_speedMonitor.average();
|
||||
|
||||
if (isSeed()) {
|
||||
if (isSeed())
|
||||
{
|
||||
const qreal maxRatioValue = maxRatio();
|
||||
const int maxSeedingTimeValue = maxSeedingTime();
|
||||
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
|
||||
|
||||
qlonglong ratioEta = MAX_ETA;
|
||||
|
||||
if ((speedAverage.upload > 0) && (maxRatioValue >= 0)) {
|
||||
if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
|
||||
{
|
||||
|
||||
qlonglong realDL = totalDownload();
|
||||
if (realDL <= 0)
|
||||
|
@ -850,7 +877,8 @@ qlonglong TorrentHandleImpl::eta() const
|
|||
|
||||
qlonglong seedingTimeEta = MAX_ETA;
|
||||
|
||||
if (maxSeedingTimeValue >= 0) {
|
||||
if (maxSeedingTimeValue >= 0)
|
||||
{
|
||||
seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime();
|
||||
if (seedingTimeEta < 0)
|
||||
seedingTimeEta = 0;
|
||||
|
@ -872,7 +900,8 @@ QVector<qreal> TorrentHandleImpl::filesProgress() const
|
|||
const int count = static_cast<int>(fp.size());
|
||||
QVector<qreal> result;
|
||||
result.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
const qlonglong size = fileSize(i);
|
||||
if ((size <= 0) || (fp[i] == size))
|
||||
result << 1;
|
||||
|
@ -995,7 +1024,8 @@ QVector<PeerInfo> TorrentHandleImpl::peers() const
|
|||
QBitArray TorrentHandleImpl::pieces() const
|
||||
{
|
||||
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}])
|
||||
result.setBit(i, true);
|
||||
}
|
||||
|
@ -1097,7 +1127,8 @@ qlonglong TorrentHandleImpl::nextAnnounce() const
|
|||
|
||||
void TorrentHandleImpl::setName(const QString &name)
|
||||
{
|
||||
if (m_name != name) {
|
||||
if (m_name != name)
|
||||
{
|
||||
m_name = name;
|
||||
m_session->handleTorrentNameChanged(this);
|
||||
}
|
||||
|
@ -1105,7 +1136,8 @@ void TorrentHandleImpl::setName(const QString &name)
|
|||
|
||||
bool TorrentHandleImpl::setCategory(const QString &category)
|
||||
{
|
||||
if (m_category != category) {
|
||||
if (m_category != category)
|
||||
{
|
||||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||
return false;
|
||||
|
||||
|
@ -1113,7 +1145,8 @@ bool TorrentHandleImpl::setCategory(const QString &category)
|
|||
m_category = category;
|
||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||
|
||||
if (m_useAutoTMM) {
|
||||
if (m_useAutoTMM)
|
||||
{
|
||||
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
|
||||
move_impl(m_session->categorySavePath(m_category), MoveStorageMode::Overwrite);
|
||||
else
|
||||
|
@ -1126,7 +1159,8 @@ bool TorrentHandleImpl::setCategory(const QString &category)
|
|||
|
||||
void TorrentHandleImpl::move(QString path)
|
||||
{
|
||||
if (m_useAutoTMM) {
|
||||
if (m_useAutoTMM)
|
||||
{
|
||||
m_useAutoTMM = false;
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
}
|
||||
|
@ -1145,10 +1179,12 @@ void TorrentHandleImpl::move_impl(QString path, const MoveStorageMode mode)
|
|||
if (path == savePath()) return;
|
||||
path = Utils::Fs::toNativePath(path);
|
||||
|
||||
if (!useTempPath()) {
|
||||
if (!useTempPath())
|
||||
{
|
||||
moveStorage(path, mode);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_savePath = path;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
@ -1172,7 +1208,8 @@ void TorrentHandleImpl::forceRecheck()
|
|||
m_hasMissingFiles = false;
|
||||
m_unchecked = false;
|
||||
|
||||
if (isPaused()) {
|
||||
if (isPaused())
|
||||
{
|
||||
// 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).
|
||||
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)
|
||||
{
|
||||
if (enable) {
|
||||
if (enable)
|
||||
{
|
||||
m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
|
||||
m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
|
||||
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
|
||||
// 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];
|
||||
if (filePrio <= lt::download_priority_t {0})
|
||||
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)
|
||||
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.last() - i] = newPrio;
|
||||
}
|
||||
|
@ -1247,7 +1288,8 @@ void TorrentHandleImpl::pause()
|
|||
|
||||
m_speedMonitor.reset();
|
||||
|
||||
if (!m_isStopped) {
|
||||
if (!m_isStopped)
|
||||
{
|
||||
m_isStopped = true;
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
|
@ -1258,12 +1300,14 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode)
|
|||
if (hasError())
|
||||
m_nativeHandle.clear_error();
|
||||
|
||||
if (m_hasMissingFiles) {
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
m_hasMissingFiles = false;
|
||||
m_nativeHandle.force_recheck();
|
||||
}
|
||||
|
||||
if (m_isStopped) {
|
||||
if (m_isStopped)
|
||||
{
|
||||
// Torrent may have been temporarily resumed to perform checking files
|
||||
// so we have to ensure it will not pause after checking is done.
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
|
||||
|
@ -1275,7 +1319,8 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode mode)
|
|||
|
||||
m_operatingMode = mode;
|
||||
|
||||
if (m_isStopped) {
|
||||
if (m_isStopped)
|
||||
{
|
||||
m_isStopped = false;
|
||||
m_session->handleTorrentResumed(this);
|
||||
}
|
||||
|
@ -1283,7 +1328,8 @@ void TorrentHandleImpl::resume(const TorrentOperatingMode 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;
|
||||
updateStatus();
|
||||
}
|
||||
|
@ -1307,7 +1353,8 @@ void TorrentHandleImpl::handleMoveStorageJobFinished(const bool hasOutstandingJo
|
|||
|
||||
updateStatus();
|
||||
const QString newPath = QString::fromStdString(m_nativeStatus.save_path);
|
||||
if (!useTempPath() && (newPath != m_savePath)) {
|
||||
if (!useTempPath() && (newPath != m_savePath))
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
@ -1363,14 +1410,16 @@ void TorrentHandleImpl::handleTorrentCheckedAlert(const lt::torrent_checked_aler
|
|||
Q_UNUSED(p);
|
||||
qDebug("\"%s\" have just finished checking", qUtf8Printable(name()));
|
||||
|
||||
if (m_fastresumeDataRejected && !m_hasMissingFiles) {
|
||||
if (m_fastresumeDataRejected && !m_hasMissingFiles)
|
||||
{
|
||||
saveResumeData();
|
||||
m_fastresumeDataRejected = false;
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
|
||||
if (!m_hasMissingFiles) {
|
||||
if (!m_hasMissingFiles)
|
||||
{
|
||||
if ((progress() < 1.0) && (wantedSize() > 0))
|
||||
m_hasSeedStatus = false;
|
||||
else if (progress() == 1.0)
|
||||
|
@ -1398,12 +1447,14 @@ void TorrentHandleImpl::handleTorrentFinishedAlert(const lt::torrent_finished_al
|
|||
manageIncompleteFiles();
|
||||
|
||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||
if (isMoveInProgress() || (m_renameCount > 0)) {
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
{
|
||||
if (recheckTorrentsOnCompletion)
|
||||
m_moveFinishedTriggers.append([this]() { forceRecheck(); });
|
||||
m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); });
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
forceRecheck();
|
||||
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)
|
||||
{
|
||||
if (p && !m_hasMissingFiles) {
|
||||
if (p && !m_hasMissingFiles)
|
||||
{
|
||||
// Update recent resume data
|
||||
m_ltAddTorrentParams = p->params;
|
||||
}
|
||||
|
||||
if (!m_isStopped) {
|
||||
if (!m_isStopped)
|
||||
{
|
||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||
// 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;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused;
|
||||
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.
|
||||
// === BEGIN DEPRECATED CODE === //
|
||||
const bool useDummyResumeData = !p;
|
||||
if (useDummyResumeData) {
|
||||
if (useDummyResumeData)
|
||||
{
|
||||
updateStatus();
|
||||
|
||||
resumeData["qBt-magnetUri"] = createMagnetURI().toStdString();
|
||||
|
@ -1489,12 +1545,14 @@ void TorrentHandleImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejec
|
|||
{
|
||||
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)
|
||||
m_hasMissingFiles = true;
|
||||
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...")
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::WARNING);
|
||||
}
|
||||
|
@ -1527,13 +1585,15 @@ void TorrentHandleImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
|||
#endif
|
||||
|
||||
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)
|
||||
break;
|
||||
++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("/")));
|
||||
oldPathParts.removeLast();
|
||||
}
|
||||
|
@ -1571,9 +1631,11 @@ void TorrentHandleImpl::handleFileCompletedAlert(const lt::file_completed_alert
|
|||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
||||
|
||||
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));
|
||||
if (name.endsWith(QB_EXT)) {
|
||||
if (name.endsWith(QB_EXT))
|
||||
{
|
||||
const QString oldName = name;
|
||||
name.chop(QB_EXT.size());
|
||||
qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name));
|
||||
|
@ -1627,7 +1689,8 @@ void TorrentHandleImpl::handleAppendExtensionToggled()
|
|||
|
||||
void TorrentHandleImpl::handleAlert(const lt::alert *a)
|
||||
{
|
||||
switch (a->type()) {
|
||||
switch (a->type())
|
||||
{
|
||||
case lt::file_renamed_alert::alert_type:
|
||||
handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
|
||||
break;
|
||||
|
@ -1680,22 +1743,28 @@ void TorrentHandleImpl::manageIncompleteFiles()
|
|||
{
|
||||
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
|
||||
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";
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < filesCount(); ++i) {
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
{
|
||||
QString name = filePath(i);
|
||||
if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) {
|
||||
if (!name.endsWith(QB_EXT)) {
|
||||
if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1))
|
||||
{
|
||||
if (!name.endsWith(QB_EXT))
|
||||
{
|
||||
const QString newName = name + QB_EXT;
|
||||
qDebug() << "Renaming" << name << "to" << newName;
|
||||
renameFile(i, newName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (name.endsWith(QB_EXT)) {
|
||||
else
|
||||
{
|
||||
if (name.endsWith(QB_EXT))
|
||||
{
|
||||
const QString oldName = name;
|
||||
name.chop(QB_EXT.size());
|
||||
qDebug() << "Renaming" << oldName << "to" << name;
|
||||
|
@ -1722,8 +1791,10 @@ void TorrentHandleImpl::adjustActualSavePath_impl()
|
|||
|
||||
if (targetDir == currentDir) return;
|
||||
|
||||
if (!needUseTempDir) {
|
||||
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()})) {
|
||||
if (!needUseTempDir)
|
||||
{
|
||||
if ((currentDir == tempDir) && (currentDir != QDir {m_session->tempPath()}))
|
||||
{
|
||||
// torrent without root folder still has it in its temporary save path
|
||||
// so its temp path isn't equal to temp path root
|
||||
const QString currentDirPath = currentDir.absolutePath();
|
||||
|
@ -1790,7 +1861,8 @@ void TorrentHandleImpl::setRatioLimit(qreal limit)
|
|||
else if (limit > MAX_RATIO)
|
||||
limit = MAX_RATIO;
|
||||
|
||||
if (m_ratioLimit != limit) {
|
||||
if (m_ratioLimit != limit)
|
||||
{
|
||||
m_ratioLimit = limit;
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
|
@ -1803,7 +1875,8 @@ void TorrentHandleImpl::setSeedingTimeLimit(int limit)
|
|||
else if (limit > MAX_SEEDING_TIME)
|
||||
limit = MAX_SEEDING_TIME;
|
||||
|
||||
if (m_seedingTimeLimit != limit) {
|
||||
if (m_seedingTimeLimit != limit)
|
||||
{
|
||||
m_seedingTimeLimit = limit;
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
|
@ -1846,10 +1919,12 @@ void TorrentHandleImpl::prioritizeFiles(const QVector<DownloadPriority> &priorit
|
|||
// 'torrent_finished_alert' and eg show tray notifications
|
||||
const QVector<qreal> progress = filesProgress();
|
||||
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)
|
||||
&& (priorities[i] > DownloadPriority::Ignored)
|
||||
&& (progress[i] < 1.0)) {
|
||||
&& (progress[i] < 1.0))
|
||||
{
|
||||
m_hasSeedStatus = false;
|
||||
break;
|
||||
}
|
||||
|
@ -1875,7 +1950,8 @@ QVector<qreal> TorrentHandleImpl::availableFileFractions() const
|
|||
QVector<qreal> res;
|
||||
res.reserve(filesCount);
|
||||
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);
|
||||
|
||||
int availablePieces = 0;
|
||||
|
|
|
@ -77,14 +77,16 @@ TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
|
|||
lt::error_code ec;
|
||||
const lt::bdecode_node node = lt::bdecode(data, ec
|
||||
, nullptr, depthLimit, tokenLimit);
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
if (error)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
TorrentInfo info {std::shared_ptr<lt::torrent_info>(new lt::torrent_info(node, ec))};
|
||||
if (ec) {
|
||||
if (ec)
|
||||
{
|
||||
if (error)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
return TorrentInfo();
|
||||
|
@ -99,28 +101,33 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
|
|||
error->clear();
|
||||
|
||||
QFile file {path};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (error)
|
||||
*error = file.errorString();
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
if (file.size() > MAX_TORRENT_SIZE) {
|
||||
if (file.size() > MAX_TORRENT_SIZE)
|
||||
{
|
||||
if (error)
|
||||
*error = tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE));
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
try {
|
||||
try
|
||||
{
|
||||
data = file.readAll();
|
||||
}
|
||||
catch (const std::bad_alloc &e) {
|
||||
catch (const std::bad_alloc &e)
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Torrent file read error: %1").arg(e.what());
|
||||
return TorrentInfo();
|
||||
}
|
||||
if (data.size() != file.size()) {
|
||||
if (data.size() != file.size())
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Torrent file read error: size mismatch");
|
||||
return TorrentInfo();
|
||||
|
@ -284,7 +291,8 @@ QVector<QUrl> TorrentInfo::urlSeeds() const
|
|||
QVector<QUrl> urlSeeds;
|
||||
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)
|
||||
urlSeeds.append(QUrl(webSeed.url.c_str()));
|
||||
}
|
||||
|
@ -353,7 +361,8 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const QString &file) const
|
|||
return {};
|
||||
|
||||
const int index = fileIndex(file);
|
||||
if (index == -1) {
|
||||
if (index == -1)
|
||||
{
|
||||
qDebug() << "Filename" << file << "was not found in torrent" << name();
|
||||
return {};
|
||||
}
|
||||
|
@ -365,7 +374,8 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
|
|||
if (!isValid())
|
||||
return {};
|
||||
|
||||
if ((fileIndex < 0) || (fileIndex >= filesCount())) {
|
||||
if ((fileIndex < 0) || (fileIndex >= filesCount()))
|
||||
{
|
||||
qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name();
|
||||
return {};
|
||||
}
|
||||
|
@ -402,7 +412,8 @@ int TorrentInfo::fileIndex(const QString &fileName) const
|
|||
QString TorrentInfo::rootFolder() const
|
||||
{
|
||||
QString rootFolder;
|
||||
for (int i = 0; i < filesCount(); ++i) {
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
{
|
||||
const QString filePath = this->filePath(i);
|
||||
if (QDir::isAbsolutePath(filePath)) continue;
|
||||
|
||||
|
@ -433,7 +444,8 @@ void TorrentInfo::stripRootFolder()
|
|||
// Solution for case of renamed root folder
|
||||
const QString path = filePath(0);
|
||||
const std::string newName = path.left(path.indexOf('/')).toStdString();
|
||||
if (files.name() != newName) {
|
||||
if (files.name() != newName)
|
||||
{
|
||||
files.set_name(newName);
|
||||
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}));
|
||||
|
|
|
@ -92,9 +92,11 @@ namespace
|
|||
QByteArray toBigEndianByteArray(const QHostAddress &addr)
|
||||
{
|
||||
// translate IP address to a sequence of bytes in big-endian order
|
||||
switch (addr.protocol()) {
|
||||
switch (addr.protocol())
|
||||
{
|
||||
case QAbstractSocket::IPv4Protocol:
|
||||
case QAbstractSocket::AnyIPProtocol: {
|
||||
case QAbstractSocket::AnyIPProtocol:
|
||||
{
|
||||
const quint32 ipv4 = addr.toIPv4Address();
|
||||
QByteArray ret;
|
||||
ret.append(static_cast<char>((ipv4 >> 24) & 0xFF))
|
||||
|
@ -104,7 +106,8 @@ namespace
|
|||
return ret;
|
||||
}
|
||||
|
||||
case QAbstractSocket::IPv6Protocol: {
|
||||
case QAbstractSocket::IPv6Protocol:
|
||||
{
|
||||
const Q_IPV6ADDR ipv6 = addr.toIPv6Address();
|
||||
QByteArray ret;
|
||||
for (const quint8 i : ipv6.c)
|
||||
|
@ -162,7 +165,8 @@ struct Tracker::TrackerAnnounceRequest
|
|||
void Tracker::TorrentStats::setPeer(const Peer &peer)
|
||||
{
|
||||
// always replace existing peer
|
||||
if (!removePeer(peer)) {
|
||||
if (!removePeer(peer))
|
||||
{
|
||||
// Too many peers, remove a random one
|
||||
if (peers.size() >= MAX_PEERS_PER_TORRENT)
|
||||
removePeer(*peers.begin());
|
||||
|
@ -198,8 +202,10 @@ bool Tracker::start()
|
|||
const QHostAddress ip = QHostAddress::Any;
|
||||
const int port = Preferences::instance()->getTrackerPort();
|
||||
|
||||
if (m_server->isListening()) {
|
||||
if (m_server->serverPort() == port) {
|
||||
if (m_server->isListening())
|
||||
{
|
||||
if (m_server->serverPort() == port)
|
||||
{
|
||||
// Already listening on the right port, just return
|
||||
return true;
|
||||
}
|
||||
|
@ -211,11 +217,13 @@ bool Tracker::start()
|
|||
// Listen on the predefined port
|
||||
const bool listenSuccess = m_server->listen(ip, port);
|
||||
|
||||
if (listenSuccess) {
|
||||
if (listenSuccess)
|
||||
{
|
||||
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
|
||||
.arg(ip.toString(), QString::number(port)), Log::INFO);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
|
||||
.arg(ip.toString(), QString::number(port), m_server->errorString())
|
||||
, Log::WARNING);
|
||||
|
@ -233,7 +241,8 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
|
|||
|
||||
status(200);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
// Is it a GET request?
|
||||
if (request.method != Http::HEADER_REQUEST_METHOD_GET)
|
||||
throw MethodNotAllowedHTTPError();
|
||||
|
@ -243,16 +252,19 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
|
|||
else
|
||||
throw NotFoundHTTPError();
|
||||
}
|
||||
catch (const HTTPError &error) {
|
||||
catch (const HTTPError &error)
|
||||
{
|
||||
status(error.statusCode(), error.statusText());
|
||||
if (!error.message().isEmpty())
|
||||
print(error.message(), Http::CONTENT_TYPE_TXT);
|
||||
}
|
||||
catch (const TrackerError &error) {
|
||||
catch (const TrackerError &error)
|
||||
{
|
||||
clear(); // clear response
|
||||
status(200);
|
||||
|
||||
const lt::entry::dictionary_type bencodedEntry = {
|
||||
const lt::entry::dictionary_type bencodedEntry =
|
||||
{
|
||||
{ANNOUNCE_RESPONSE_FAILURE_REASON, {error.what()}}
|
||||
};
|
||||
QByteArray reply;
|
||||
|
@ -312,7 +324,8 @@ void Tracker::processAnnounceRequest()
|
|||
|
||||
// 4. numwant
|
||||
const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT);
|
||||
if (numWantIter != queryParams.end()) {
|
||||
if (numWantIter != queryParams.end())
|
||||
{
|
||||
const int num = numWantIter->toInt();
|
||||
if (num < 0)
|
||||
throw TrackerError("Invalid \"numwant\" parameter");
|
||||
|
@ -348,15 +361,18 @@ void Tracker::processAnnounceRequest()
|
|||
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_EMPTY)
|
||||
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_COMPLETED)
|
||||
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_STARTED)
|
||||
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED)) {
|
||||
|| (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED))
|
||||
{
|
||||
// [BEP-21] Extension for partial seeds
|
||||
// (partial support - we don't support BEP-48 so the part that concerns that is not supported)
|
||||
registerPeer(announceReq);
|
||||
}
|
||||
else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED) {
|
||||
else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED)
|
||||
{
|
||||
unregisterPeer(announceReq);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
throw TrackerError("Invalid \"event\" parameter");
|
||||
}
|
||||
|
||||
|
@ -365,7 +381,8 @@ void Tracker::processAnnounceRequest()
|
|||
|
||||
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
|
||||
if (m_torrents.size() >= MAX_TORRENTS)
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
|
@ -390,7 +407,8 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
|||
{
|
||||
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
|
||||
|
||||
lt::entry::dictionary_type replyDict {
|
||||
lt::entry::dictionary_type replyDict
|
||||
{
|
||||
{ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL},
|
||||
{ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders},
|
||||
{ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)},
|
||||
|
@ -402,13 +420,16 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
|||
// peer list
|
||||
// [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23)
|
||||
// [BEP-23] Tracker Returns Compact Peer Lists
|
||||
if (announceReq.compact) {
|
||||
if (announceReq.compact)
|
||||
{
|
||||
lt::entry::string_type peers;
|
||||
lt::entry::string_type peers6;
|
||||
|
||||
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED) {
|
||||
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
|
||||
{
|
||||
int counter = 0;
|
||||
for (const Peer &peer : asConst(torrentStats.peers)) {
|
||||
for (const Peer &peer : asConst(torrentStats.peers))
|
||||
{
|
||||
if (counter++ >= announceReq.numwant)
|
||||
break;
|
||||
|
||||
|
@ -423,16 +444,20 @@ void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
|||
if (!peers6.empty())
|
||||
replyDict[ANNOUNCE_RESPONSE_PEERS6] = peers6;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
lt::entry::list_type peerList;
|
||||
|
||||
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED) {
|
||||
if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
|
||||
{
|
||||
int counter = 0;
|
||||
for (const Peer &peer : torrentStats.peers) {
|
||||
for (const Peer &peer : torrentStats.peers)
|
||||
{
|
||||
if (counter++ >= announceReq.numwant)
|
||||
break;
|
||||
|
||||
lt::entry::dictionary_type peerDict = {
|
||||
lt::entry::dictionary_type peerDict =
|
||||
{
|
||||
{ANNOUNCE_RESPONSE_PEERS_IP, peer.address},
|
||||
{ANNOUNCE_RESPONSE_PEERS_PORT, peer.port}
|
||||
};
|
||||
|
|
|
@ -107,7 +107,8 @@ void TrackerEntry::setTier(const int value)
|
|||
int TrackerEntry::numSeeds() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) {
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_complete);
|
||||
|
@ -121,7 +122,8 @@ int TrackerEntry::numSeeds() const
|
|||
int TrackerEntry::numLeeches() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) {
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_incomplete);
|
||||
|
@ -135,7 +137,8 @@ int TrackerEntry::numLeeches() const
|
|||
int TrackerEntry::numDownloaded() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints) {
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_downloaded);
|
||||
|
|
|
@ -76,7 +76,8 @@ void FileSystemWatcher::addPath(const QString &path)
|
|||
if (!dir.exists()) return;
|
||||
|
||||
// Check if the path points to a network file system or not
|
||||
if (Utils::Fs::isNetworkFileSystem(path)) {
|
||||
if (Utils::Fs::isNetworkFileSystem(path))
|
||||
{
|
||||
// Network mode
|
||||
LogMsg(tr("Watching remote folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
|
||||
m_watchedFolders << dir;
|
||||
|
@ -94,7 +95,8 @@ void FileSystemWatcher::addPath(const QString &path)
|
|||
|
||||
void FileSystemWatcher::removePath(const QString &path)
|
||||
{
|
||||
if (m_watchedFolders.removeOne(path)) {
|
||||
if (m_watchedFolders.removeOne(path))
|
||||
{
|
||||
if (m_watchedFolders.isEmpty())
|
||||
m_watchTimer.stop();
|
||||
return;
|
||||
|
@ -125,12 +127,14 @@ void FileSystemWatcher::processPartialTorrents()
|
|||
if (!QFile::exists(torrentPath))
|
||||
return true;
|
||||
|
||||
if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) {
|
||||
if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid())
|
||||
{
|
||||
noLongerPartial << torrentPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value >= MAX_PARTIAL_RETRIES) {
|
||||
if (value >= MAX_PARTIAL_RETRIES)
|
||||
{
|
||||
QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
|
||||
return true;
|
||||
}
|
||||
|
@ -140,11 +144,13 @@ void FileSystemWatcher::processPartialTorrents()
|
|||
});
|
||||
|
||||
// Stop the partial timer if necessary
|
||||
if (m_partialTorrents.empty()) {
|
||||
if (m_partialTorrents.empty())
|
||||
{
|
||||
m_partialTorrentTimer.stop();
|
||||
qDebug("No longer any partial torrent.");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
|
||||
m_partialTorrentTimer.start(WATCH_INTERVAL);
|
||||
}
|
||||
|
@ -158,7 +164,8 @@ void FileSystemWatcher::processTorrentsInDir(const QDir &dir)
|
|||
{
|
||||
QStringList torrents;
|
||||
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);
|
||||
if (file.endsWith(".magnet", Qt::CaseInsensitive))
|
||||
torrents << fileAbsPath;
|
||||
|
|
|
@ -59,13 +59,17 @@ void Connection::read()
|
|||
m_idleTimer.restart();
|
||||
m_receivedData.append(m_socket->readAll());
|
||||
|
||||
while (!m_receivedData.isEmpty()) {
|
||||
while (!m_receivedData.isEmpty())
|
||||
{
|
||||
const RequestParser::ParseResult result = RequestParser::parse(m_receivedData);
|
||||
|
||||
switch (result.status) {
|
||||
case RequestParser::ParseStatus::Incomplete: {
|
||||
switch (result.status)
|
||||
{
|
||||
case RequestParser::ParseStatus::Incomplete:
|
||||
{
|
||||
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")
|
||||
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
|
||||
|
@ -78,7 +82,8 @@ void Connection::read()
|
|||
}
|
||||
return;
|
||||
|
||||
case RequestParser::ParseStatus::BadRequest: {
|
||||
case RequestParser::ParseStatus::BadRequest:
|
||||
{
|
||||
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1")
|
||||
.arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
|
||||
|
@ -90,7 +95,8 @@ void Connection::read()
|
|||
}
|
||||
return;
|
||||
|
||||
case RequestParser::ParseStatus::OK: {
|
||||
case RequestParser::ParseStatus::OK:
|
||||
{
|
||||
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
|
||||
|
||||
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
|
||||
{
|
||||
for (const QStringRef &str : list) {
|
||||
for (const QStringRef &str : list)
|
||||
{
|
||||
if (!str.startsWith(encoding))
|
||||
continue;
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ namespace
|
|||
{
|
||||
// [rfc7230] 3.2. Header Fields
|
||||
const int i = line.indexOf(':');
|
||||
if (i <= 0) {
|
||||
if (i <= 0)
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
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
|
||||
const int headerEnd = data.indexOf(EOH);
|
||||
if (headerEnd < 0) {
|
||||
if (headerEnd < 0)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return {ParseStatus::Incomplete, Request(), 0};
|
||||
}
|
||||
|
||||
const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
|
||||
if (!parseStartLines(httpHeaders)) {
|
||||
if (!parseStartLines(httpHeaders))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
}
|
||||
|
@ -104,26 +107,32 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
|
|||
// handle supported methods
|
||||
if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD))
|
||||
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;
|
||||
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";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
}
|
||||
if (contentLength > MAX_CONTENT_SIZE) {
|
||||
if (contentLength > MAX_CONTENT_SIZE)
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
}
|
||||
|
||||
if (contentLength > 0) {
|
||||
if (contentLength > 0)
|
||||
{
|
||||
const QByteArray httpBodyView = midView(data, headerLength, contentLength);
|
||||
if (httpBodyView.length() < contentLength) {
|
||||
if (httpBodyView.length() < contentLength)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return {ParseStatus::Incomplete, Request(), 0};
|
||||
}
|
||||
|
||||
if (!parsePostMessage(httpBodyView)) {
|
||||
if (!parsePostMessage(httpBodyView))
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "message body parsing error";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
}
|
||||
|
@ -143,12 +152,15 @@ bool RequestParser::parseStartLines(const QString &data)
|
|||
|
||||
// [rfc7230] 3.2.2. Field Order
|
||||
QStringList requestLines;
|
||||
for (const auto &line : lines) {
|
||||
if (line.at(0).isSpace() && !requestLines.isEmpty()) {
|
||||
for (const auto &line : lines)
|
||||
{
|
||||
if (line.at(0).isSpace() && !requestLines.isEmpty())
|
||||
{
|
||||
// continuation of previous line
|
||||
requestLines.last() += line.toString();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
requestLines += line.toString();
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +171,8 @@ bool RequestParser::parseStartLines(const QString &data)
|
|||
if (!parseRequestLine(requestLines[0]))
|
||||
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))
|
||||
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 QRegularExpressionMatch match = re.match(line);
|
||||
|
||||
if (!match.hasMatch()) {
|
||||
if (!match.hasMatch())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
@ -189,12 +203,14 @@ bool RequestParser::parseRequestLine(const QString &line)
|
|||
|
||||
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent));
|
||||
|
||||
if (sepPos >= 0) {
|
||||
if (sepPos >= 0)
|
||||
{
|
||||
const QByteArray query = midView(url, (sepPos + 1));
|
||||
|
||||
// [rfc3986] 2.4 When to Encode or Decode
|
||||
// URL components should be separated before percent-decoding
|
||||
for (const QByteArray ¶m : asConst(splitToViews(query, "&"))) {
|
||||
for (const QByteArray ¶m : asConst(splitToViews(query, "&")))
|
||||
{
|
||||
const int eqCharPos = param.indexOf('=');
|
||||
if (eqCharPos <= 0) continue; // ignores params without name
|
||||
|
||||
|
@ -222,12 +238,14 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
|||
const QString contentTypeLower = contentType.toLower();
|
||||
|
||||
// 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
|
||||
const QByteArray processedData = QByteArray(data).replace('+', ' ');
|
||||
|
||||
QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded));
|
||||
while (i.hasNext()) {
|
||||
while (i.hasNext())
|
||||
{
|
||||
const QStringPair pair = i.next();
|
||||
m_request.posts[pair.first] = pair.second;
|
||||
}
|
||||
|
@ -236,19 +254,22 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
|||
}
|
||||
|
||||
// multipart/form-data
|
||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA)) {
|
||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA))
|
||||
{
|
||||
// [rfc2046] 5.1.1. Common Syntax
|
||||
|
||||
// find boundary delimiter
|
||||
const QLatin1String boundaryFieldName("boundary=");
|
||||
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!";
|
||||
return false;
|
||||
}
|
||||
|
||||
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!";
|
||||
return false;
|
||||
}
|
||||
|
@ -256,7 +277,8 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
|||
// split data by "dash-boundary"
|
||||
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
|
||||
QVector<QByteArray> multipart = splitToViews(data, dashDelimiter, QString::SkipEmptyParts);
|
||||
if (multipart.isEmpty()) {
|
||||
if (multipart.isEmpty())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "multipart empty";
|
||||
return false;
|
||||
}
|
||||
|
@ -279,7 +301,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
|
|||
{
|
||||
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";
|
||||
return false;
|
||||
}
|
||||
|
@ -289,12 +312,15 @@ bool RequestParser::parseFormData(const QByteArray &data)
|
|||
|
||||
HeaderMap headersMap;
|
||||
const QVector<QStringRef> headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts);
|
||||
for (const auto &line : headerLines) {
|
||||
if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive)) {
|
||||
for (const auto &line : headerLines)
|
||||
{
|
||||
if (line.trimmed().startsWith(HEADER_CONTENT_DISPOSITION, Qt::CaseInsensitive))
|
||||
{
|
||||
// extract out filename & name
|
||||
const QVector<QStringRef> directives = line.split(';', QString::SkipEmptyParts);
|
||||
|
||||
for (const auto &directive : directives) {
|
||||
for (const auto &directive : directives)
|
||||
{
|
||||
const int idx = directive.indexOf('=');
|
||||
if (idx < 0)
|
||||
continue;
|
||||
|
@ -304,7 +330,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
|
|||
headersMap[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (!parseHeaderLine(line.toString(), headersMap))
|
||||
return false;
|
||||
}
|
||||
|
@ -314,13 +341,16 @@ bool RequestParser::parseFormData(const QByteArray &data)
|
|||
const QLatin1String filename("filename");
|
||||
const QLatin1String name("name");
|
||||
|
||||
if (headersMap.contains(filename)) {
|
||||
if (headersMap.contains(filename))
|
||||
{
|
||||
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;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// malformed
|
||||
qWarning() << Q_FUNC_INFO << "multipart/form-data header error";
|
||||
return false;
|
||||
|
|
|
@ -93,12 +93,14 @@ void Server::incomingConnection(const qintptr socketDescriptor)
|
|||
else
|
||||
serverSocket = new QTcpSocket(this);
|
||||
|
||||
if (!serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
||||
{
|
||||
delete serverSocket;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_https) {
|
||||
if (m_https)
|
||||
{
|
||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
||||
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 QSslKey key {Utils::Net::loadSSLKey(privateKey)};
|
||||
|
||||
if (certs.isEmpty() || key.isNull()) {
|
||||
if (certs.isEmpty() || key.isNull())
|
||||
{
|
||||
disableHttps();
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,8 @@ DNSUpdater::DNSUpdater(QObject *parent)
|
|||
|
||||
// Check lastUpdate to avoid flooding
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -82,30 +83,36 @@ void DNSUpdater::checkPublicIP()
|
|||
|
||||
void DNSUpdater::ipRequestFinished(const DownloadResult &result)
|
||||
{
|
||||
if (result.status != DownloadStatus::Success) {
|
||||
if (result.status != DownloadStatus::Success)
|
||||
{
|
||||
qWarning() << "IP request failed:" << result.errorString;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response
|
||||
const QRegularExpressionMatch ipRegexMatch = QRegularExpression("Current IP Address:\\s+([^<]+)</body>").match(result.data);
|
||||
if (ipRegexMatch.hasMatch()) {
|
||||
if (ipRegexMatch.hasMatch())
|
||||
{
|
||||
QString ipStr = ipRegexMatch.captured(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
|
||||
QHostAddress newIp(ipStr);
|
||||
if (!newIp.isNull()) {
|
||||
if (m_lastIP != newIp) {
|
||||
if (!newIp.isNull())
|
||||
{
|
||||
if (m_lastIP != newIp)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "The IP address changed, report the change to DynDNS...";
|
||||
qDebug() << m_lastIP.toString() << "->" << newIp.toString();
|
||||
m_lastIP = newIp;
|
||||
updateDNSService();
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +140,8 @@ QString DNSUpdater::getUpdateUrl() const
|
|||
|
||||
Q_ASSERT(!m_lastIP.isNull());
|
||||
// Service specific
|
||||
switch (m_service) {
|
||||
switch (m_service)
|
||||
{
|
||||
case DNS::DYNDNS:
|
||||
url.setHost("members.dyndns.org");
|
||||
break;
|
||||
|
@ -171,12 +179,14 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
|||
const QString code = reply.split(' ').first();
|
||||
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);
|
||||
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);
|
||||
m_lastIP.clear();
|
||||
// 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
|
||||
m_ipCheckTimer.stop();
|
||||
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);
|
||||
m_state = INVALID_CREDS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "badauth") {
|
||||
if (code == "badauth")
|
||||
{
|
||||
logger->addMessage(tr("Dynamic DNS error: Invalid username/password."), Log::CRITICAL);
|
||||
m_state = INVALID_CREDS;
|
||||
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."),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
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"),
|
||||
Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "abuse") {
|
||||
if (code == "abuse")
|
||||
{
|
||||
logger->addMessage(tr("Dynamic DNS error: Your username was blocked due to abuse."), Log::CRITICAL);
|
||||
m_state = FATAL;
|
||||
}
|
||||
|
@ -225,14 +240,17 @@ void DNSUpdater::updateCredentials()
|
|||
Logger *const logger = Logger::instance();
|
||||
bool change = false;
|
||||
// Get DNS service information
|
||||
if (m_service != pref->getDynDNSService()) {
|
||||
if (m_service != pref->getDynDNSService())
|
||||
{
|
||||
m_service = pref->getDynDNSService();
|
||||
change = true;
|
||||
}
|
||||
if (m_domain != pref->getDynDomainName()) {
|
||||
if (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);
|
||||
if (!domainRegexMatch.hasMatch()) {
|
||||
if (!domainRegexMatch.hasMatch())
|
||||
{
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
|
@ -241,9 +259,11 @@ void DNSUpdater::updateCredentials()
|
|||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_username != pref->getDynDNSUsername()) {
|
||||
if (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);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
|
@ -252,9 +272,11 @@ void DNSUpdater::updateCredentials()
|
|||
}
|
||||
change = true;
|
||||
}
|
||||
if (m_password != pref->getDynDNSPassword()) {
|
||||
if (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);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
|
@ -264,7 +286,8 @@ void DNSUpdater::updateCredentials()
|
|||
change = true;
|
||||
}
|
||||
|
||||
if ((m_state == INVALID_CREDS) && change) {
|
||||
if ((m_state == INVALID_CREDS) && change)
|
||||
{
|
||||
m_state = OK; // Try again
|
||||
m_ipCheckTimer.start();
|
||||
checkPublicIP();
|
||||
|
@ -273,7 +296,8 @@ void DNSUpdater::updateCredentials()
|
|||
|
||||
QUrl DNSUpdater::getRegistrationUrl(const int service)
|
||||
{
|
||||
switch (service) {
|
||||
switch (service)
|
||||
{
|
||||
case DNS::DYNDNS:
|
||||
return {"https://www.dyndns.com/account/services/hosts/add.html"};
|
||||
case DNS::NOIP:
|
||||
|
|
|
@ -66,10 +66,12 @@ DownloadHandlerImpl::DownloadHandlerImpl(Net::DownloadManager *manager, const Ne
|
|||
|
||||
void DownloadHandlerImpl::cancel()
|
||||
{
|
||||
if (m_reply) {
|
||||
if (m_reply)
|
||||
{
|
||||
m_reply->abort();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
setError(errorCodeToString(QNetworkReply::OperationCanceledError));
|
||||
finish();
|
||||
}
|
||||
|
@ -103,7 +105,8 @@ void DownloadHandlerImpl::processFinishedDownload()
|
|||
qDebug("Download finished: %s", qUtf8Printable(url()));
|
||||
|
||||
// Check if the request was successful
|
||||
if (m_reply->error() != QNetworkReply::NoError) {
|
||||
if (m_reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
// Failure
|
||||
qDebug("Download failure (%s), reason: %s", qUtf8Printable(url()), qUtf8Printable(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
|
||||
const QVariant redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (redirection.isValid()) {
|
||||
if (redirection.isValid())
|
||||
{
|
||||
handleRedirection(redirection.toUrl());
|
||||
return;
|
||||
}
|
||||
|
@ -123,7 +127,8 @@ void DownloadHandlerImpl::processFinishedDownload()
|
|||
? Utils::Gzip::decompress(m_reply->readAll())
|
||||
: m_reply->readAll();
|
||||
|
||||
if (m_downloadRequest.saveToFile()) {
|
||||
if (m_downloadRequest.saveToFile())
|
||||
{
|
||||
QString filePath;
|
||||
if (saveToFile(m_result.data, filePath))
|
||||
m_result.filePath = filePath;
|
||||
|
@ -136,13 +141,15 @@ void DownloadHandlerImpl::processFinishedDownload()
|
|||
|
||||
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
|
||||
disconnect(m_reply, &QNetworkReply::downloadProgress, this, &DownloadHandlerImpl::checkDownloadSize);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit())) {
|
||||
if ((bytesTotal > m_downloadRequest.limit()) || (bytesReceived > m_downloadRequest.limit()))
|
||||
{
|
||||
m_reply->abort();
|
||||
setError(tr("The file size (%1) exceeds the download limit (%2)")
|
||||
.arg(Utils::Misc::friendlyUnit(bytesTotal)
|
||||
|
@ -153,7 +160,8 @@ void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qi
|
|||
|
||||
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));
|
||||
finish();
|
||||
return;
|
||||
|
@ -165,7 +173,8 @@ void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
|
|||
qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
|
||||
|
||||
// Redirect to magnet workaround
|
||||
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) {
|
||||
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive))
|
||||
{
|
||||
qDebug("Magnet redirect detected.");
|
||||
m_result.status = Net::DownloadStatus::RedirectedToMagnet;
|
||||
m_result.magnet = newUrlString;
|
||||
|
@ -199,7 +208,8 @@ void DownloadHandlerImpl::finish()
|
|||
|
||||
QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status)
|
||||
{
|
||||
switch (status) {
|
||||
switch (status)
|
||||
{
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
return tr("The remote host name was not found (invalid hostname)");
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
|
|
|
@ -60,7 +60,8 @@ namespace
|
|||
{
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
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))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
@ -72,7 +73,8 @@ namespace
|
|||
{
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = allCookies();
|
||||
for (const QNetworkCookie &cookie : asConst(allCookies())) {
|
||||
for (const QNetworkCookie &cookie : asConst(allCookies()))
|
||||
{
|
||||
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
@ -87,7 +89,8 @@ namespace
|
|||
{
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
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))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
@ -99,7 +102,8 @@ namespace
|
|||
{
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = cookieList;
|
||||
for (const QNetworkCookie &cookie : cookieList) {
|
||||
for (const QNetworkCookie &cookie : cookieList)
|
||||
{
|
||||
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
|
@ -172,10 +176,12 @@ Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &down
|
|||
m_waitingJobs[id].removeOne(downloadHandler);
|
||||
});
|
||||
|
||||
if (isSequentialService && m_busyServices.contains(id)) {
|
||||
if (isSequentialService && m_busyServices.contains(id))
|
||||
{
|
||||
m_waitingJobs[id].enqueue(downloadHandler);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url()));
|
||||
if (isSequentialService)
|
||||
m_busyServices.insert(id);
|
||||
|
@ -230,27 +236,32 @@ void Net::DownloadManager::applyProxySettings()
|
|||
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
|
||||
QNetworkProxy proxy;
|
||||
|
||||
if (!proxyManager->isProxyOnlyForTorrents() && (proxyConfig.type != ProxyType::None)) {
|
||||
if (!proxyManager->isProxyOnlyForTorrents() && (proxyConfig.type != ProxyType::None))
|
||||
{
|
||||
// Proxy enabled
|
||||
proxy.setHostName(proxyConfig.ip);
|
||||
proxy.setPort(proxyConfig.port);
|
||||
// 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";
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
|
||||
proxy.setType(QNetworkProxy::HttpProxy);
|
||||
}
|
||||
// Authentication?
|
||||
if (proxyManager->isAuthenticationRequired()) {
|
||||
if (proxyManager->isAuthenticationRequired())
|
||||
{
|
||||
qDebug("Proxy requires authentication, authenticating...");
|
||||
proxy.setUser(proxyConfig.username);
|
||||
proxy.setPassword(proxyConfig.password);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
proxy.setType(QNetworkProxy::NoProxy);
|
||||
}
|
||||
|
||||
|
@ -264,7 +275,8 @@ void Net::DownloadManager::handleReplyFinished(const QNetworkReply *reply)
|
|||
// in the case when the redirection occurred.
|
||||
const ServiceID id = ServiceID::fromURL(reply->request().url());
|
||||
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
|
||||
m_busyServices.remove(id);
|
||||
return;
|
||||
|
|
|
@ -88,26 +88,30 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
|||
{
|
||||
GeoIPDatabase *db = nullptr;
|
||||
QFile file(filename);
|
||||
if (file.size() > MAX_FILE_SIZE) {
|
||||
if (file.size() > MAX_FILE_SIZE)
|
||||
{
|
||||
error = tr("Unsupported database file size.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
error = file.errorString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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();
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error))
|
||||
{
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -118,7 +122,8 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
|||
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
{
|
||||
GeoIPDatabase *db = nullptr;
|
||||
if (data.size() > MAX_FILE_SIZE) {
|
||||
if (data.size() > MAX_FILE_SIZE)
|
||||
{
|
||||
error = tr("Unsupported database file size.");
|
||||
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);
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error))
|
||||
{
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -161,8 +167,10 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
|
|||
|
||||
const uchar *ptr = m_data;
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
const bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1);
|
||||
// Interpret the left/right record as number
|
||||
if (right)
|
||||
|
@ -173,16 +181,20 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
|
|||
memcpy(&idPtr[4 - m_recordBytes], ptr, m_recordBytes);
|
||||
fromBigEndian(idPtr, 4);
|
||||
|
||||
if (id == m_nodeCount) {
|
||||
if (id == m_nodeCount)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (id > m_nodeCount) {
|
||||
if (id > m_nodeCount)
|
||||
{
|
||||
QString country = m_countries.value(id);
|
||||
if (country.isEmpty()) {
|
||||
if (country.isEmpty())
|
||||
{
|
||||
const quint32 offset = id - m_nodeCount - sizeof(DATA_SECTION_SEPARATOR);
|
||||
quint32 tmp = offset + m_indexSize + sizeof(DATA_SECTION_SEPARATOR);
|
||||
const QVariant val = readDataField(tmp);
|
||||
if (val.userType() == QMetaType::QVariantHash) {
|
||||
if (val.userType() == QMetaType::QVariantHash)
|
||||
{
|
||||
country = val.toHash()["country"].toHash()["iso_code"].toString();
|
||||
m_countries[id] = country;
|
||||
}
|
||||
|
@ -198,18 +210,22 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
|
|||
}
|
||||
|
||||
#define CHECK_METADATA_REQ(key, type) \
|
||||
if (!metadata.contains(#key)) { \
|
||||
if (!metadata.contains(#key)) \
|
||||
{ \
|
||||
error = errMsgNotFound.arg(#key); \
|
||||
return false; \
|
||||
} \
|
||||
if (metadata.value(#key).userType() != QMetaType::type) { \
|
||||
if (metadata.value(#key).userType() != QMetaType::type) \
|
||||
{ \
|
||||
error = errMsgInvalid.arg(#key); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define CHECK_METADATA_OPT(key, type) \
|
||||
if (metadata.contains(#key)) { \
|
||||
if (metadata.value(#key).userType() != QMetaType::type) { \
|
||||
if (metadata.contains(#key)) \
|
||||
{ \
|
||||
if (metadata.value(#key).userType() != QMetaType::type) \
|
||||
{ \
|
||||
error = errMsgInvalid.arg(#key); \
|
||||
return false; \
|
||||
} \
|
||||
|
@ -226,21 +242,24 @@ bool GeoIPDatabase::parseMetadata(const QVariantHash &metadata, QString &error)
|
|||
CHECK_METADATA_REQ(binary_format_minor_version, UShort);
|
||||
const uint versionMajor = metadata.value("binary_format_major_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);
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_METADATA_REQ(ip_version, UShort);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_METADATA_REQ(record_size, UShort);
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
@ -270,7 +289,8 @@ bool GeoIPDatabase::loadDB(QString &error) const
|
|||
const int nodeSize = m_recordSize / 4; // in bytes
|
||||
const int indexSize = m_nodeCount * nodeSize;
|
||||
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.");
|
||||
return false;
|
||||
}
|
||||
|
@ -282,14 +302,16 @@ QVariantHash GeoIPDatabase::readMetadata() const
|
|||
{
|
||||
const char *ptr = reinterpret_cast<const char *>(m_data);
|
||||
quint32 size = m_size;
|
||||
if (m_size > MAX_METADATA_SIZE) {
|
||||
if (m_size > MAX_METADATA_SIZE)
|
||||
{
|
||||
ptr += m_size - MAX_METADATA_SIZE;
|
||||
size = MAX_METADATA_SIZE;
|
||||
}
|
||||
|
||||
const QByteArray data = QByteArray::fromRawData(ptr, size);
|
||||
int index = data.lastIndexOf(METADATA_BEGIN_MARK);
|
||||
if (index >= 0) {
|
||||
if (index >= 0)
|
||||
{
|
||||
if (m_size > MAX_METADATA_SIZE)
|
||||
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
|
||||
auto offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
|
||||
|
@ -309,7 +331,8 @@ QVariant GeoIPDatabase::readDataField(quint32 &offset) const
|
|||
|
||||
quint32 locOffset = offset;
|
||||
bool usePointer = false;
|
||||
if (descr.fieldType == DataType::Pointer) {
|
||||
if (descr.fieldType == DataType::Pointer)
|
||||
{
|
||||
usePointer = true;
|
||||
// convert offset from data section to global
|
||||
locOffset = descr.offset + (m_nodeCount * m_recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
|
||||
|
@ -318,7 +341,8 @@ QVariant GeoIPDatabase::readDataField(quint32 &offset) const
|
|||
}
|
||||
|
||||
QVariant fieldValue;
|
||||
switch (descr.fieldType) {
|
||||
switch (descr.fieldType)
|
||||
{
|
||||
case DataType::Pointer:
|
||||
qDebug() << "* Illegal Pointer using";
|
||||
break;
|
||||
|
@ -388,7 +412,8 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
|
|||
if (availSize < 1) return false;
|
||||
|
||||
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);
|
||||
if (availSize < (size + 2)) return false;
|
||||
|
||||
|
@ -406,28 +431,34 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
|
|||
}
|
||||
|
||||
out.fieldSize = dataPtr[0] & 0x1F;
|
||||
if (out.fieldSize <= 28) {
|
||||
if (out.fieldType == DataType::Unknown) {
|
||||
if (out.fieldSize <= 28)
|
||||
{
|
||||
if (out.fieldType == DataType::Unknown)
|
||||
{
|
||||
out.fieldType = static_cast<DataType>(dataPtr[1] + 7);
|
||||
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3))
|
||||
return false;
|
||||
offset += 2;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
else if (out.fieldSize == 29) {
|
||||
else if (out.fieldSize == 29)
|
||||
{
|
||||
if (availSize < 2) return false;
|
||||
out.fieldSize = dataPtr[1] + 29;
|
||||
offset += 2;
|
||||
}
|
||||
else if (out.fieldSize == 30) {
|
||||
else if (out.fieldSize == 30)
|
||||
{
|
||||
if (availSize < 3) return false;
|
||||
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
|
||||
offset += 3;
|
||||
}
|
||||
else if (out.fieldSize == 31) {
|
||||
else if (out.fieldSize == 31)
|
||||
{
|
||||
if (availSize < 4) return false;
|
||||
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821;
|
||||
offset += 4;
|
||||
|
@ -450,7 +481,8 @@ QVariant GeoIPDatabase::readMapValue(quint32 &offset, const quint32 count) const
|
|||
{
|
||||
QVariantHash map;
|
||||
|
||||
for (quint32 i = 0; i < count; ++i) {
|
||||
for (quint32 i = 0; i < count; ++i)
|
||||
{
|
||||
QVariant field = readDataField(offset);
|
||||
if (field.userType() != QMetaType::QString)
|
||||
return {};
|
||||
|
@ -470,7 +502,8 @@ QVariant GeoIPDatabase::readArrayValue(quint32 &offset, const quint32 count) con
|
|||
{
|
||||
QVariantList array;
|
||||
|
||||
for (quint32 i = 0; i < count; ++i) {
|
||||
for (quint32 i = 0; i < count; ++i)
|
||||
{
|
||||
const QVariant field = readDataField(offset);
|
||||
if (field.userType() == QVariant::Invalid)
|
||||
return {};
|
||||
|
|
|
@ -74,7 +74,8 @@ private:
|
|||
const uchar *const data = m_data + 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'
|
||||
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
|
||||
memcpy(dst, data, len);
|
||||
|
|
|
@ -140,7 +140,8 @@ QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
|
|||
|
||||
QString GeoIPManager::CountryName(const QString &countryISOCode)
|
||||
{
|
||||
static const QHash<QString, QString> countries = {
|
||||
static const QHash<QString, QString> countries =
|
||||
{
|
||||
// ISO 3166-1 alpha-2 codes
|
||||
// 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()
|
||||
{
|
||||
const bool enabled = Preferences::instance()->resolvePeerCountries();
|
||||
if (m_enabled != enabled) {
|
||||
if (m_enabled != enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
if (m_enabled && !m_geoIPDatabase) {
|
||||
if (m_enabled && !m_geoIPDatabase)
|
||||
{
|
||||
loadDatabase();
|
||||
}
|
||||
else if (!m_enabled) {
|
||||
else if (!m_enabled)
|
||||
{
|
||||
delete m_geoIPDatabase;
|
||||
m_geoIPDatabase = nullptr;
|
||||
}
|
||||
|
@ -418,22 +422,26 @@ void GeoIPManager::configure()
|
|||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const QByteArray data = Utils::Gzip::decompress(result.data, &ok);
|
||||
if (!ok) {
|
||||
if (!ok)
|
||||
{
|
||||
LogMsg(tr("Could not decompress IP geolocation database file."), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error);
|
||||
if (geoIPDatabase) {
|
||||
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch())) {
|
||||
if (geoIPDatabase)
|
||||
{
|
||||
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch()))
|
||||
{
|
||||
delete m_geoIPDatabase;
|
||||
m_geoIPDatabase = geoIPDatabase;
|
||||
LogMsg(tr("IP geolocation database loaded. Type: %1. Build time: %2.")
|
||||
|
@ -449,11 +457,13 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
|
|||
else
|
||||
LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
delete geoIPDatabase;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't load IP geolocation database. Reason: %1").arg(error), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,8 @@ ProxyConfiguration ProxyConfigurationManager::proxyConfiguration() const
|
|||
|
||||
void ProxyConfigurationManager::setProxyConfiguration(const ProxyConfiguration &config)
|
||||
{
|
||||
if (config != m_config) {
|
||||
if (config != m_config)
|
||||
{
|
||||
m_config = config;
|
||||
settings()->storeValue(KEY_TYPE, static_cast<int>(config.type));
|
||||
settings()->storeValue(KEY_IP, config.ip);
|
||||
|
@ -120,7 +121,8 @@ bool ProxyConfigurationManager::isProxyOnlyForTorrents() const
|
|||
|
||||
void ProxyConfigurationManager::setProxyOnlyForTorrents(bool onlyForTorrents)
|
||||
{
|
||||
if (m_isProxyOnlyForTorrents != onlyForTorrents) {
|
||||
if (m_isProxyOnlyForTorrents != onlyForTorrents)
|
||||
{
|
||||
settings()->storeValue(KEY_ONLY_FOR_TORRENTS, onlyForTorrents);
|
||||
m_isProxyOnlyForTorrents = onlyForTorrents;
|
||||
}
|
||||
|
@ -136,8 +138,10 @@ void ProxyConfigurationManager::configureProxy()
|
|||
{
|
||||
// Define environment variables for urllib in search engine plugins
|
||||
QString proxyStrHTTP, proxyStrSOCK;
|
||||
if (!m_isProxyOnlyForTorrents) {
|
||||
switch (m_config.type) {
|
||||
if (!m_isProxyOnlyForTorrents)
|
||||
{
|
||||
switch (m_config.type)
|
||||
{
|
||||
case ProxyType::HTTP_PW:
|
||||
proxyStrHTTP = QString::fromLatin1("http://%1:%2@%3:%4").arg(m_config.username
|
||||
, m_config.password, m_config.ip, QString::number(m_config.port));
|
||||
|
|
|
@ -60,7 +60,8 @@ ReverseResolution::~ReverseResolution()
|
|||
void ReverseResolution::resolve(const QHostAddress &ip)
|
||||
{
|
||||
const QString *hostname = m_cache.object(ip);
|
||||
if (hostname) {
|
||||
if (hostname)
|
||||
{
|
||||
emit ipResolved(ip, *hostname);
|
||||
return;
|
||||
}
|
||||
|
@ -74,7 +75,8 @@ void ReverseResolution::hostResolved(const QHostInfo &host)
|
|||
{
|
||||
const QHostAddress ip = m_lookups.take(host.lookupId());
|
||||
|
||||
if (host.error() != QHostInfo::NoError) {
|
||||
if (host.error() != QHostInfo::NoError)
|
||||
{
|
||||
emit ipResolved(ip, {});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,8 @@ namespace
|
|||
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
|
||||
// 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
|
||||
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;
|
||||
|
||||
if (needToRegisterMetaType) {
|
||||
if (needToRegisterMetaType)
|
||||
{
|
||||
qRegisterMetaType<QAbstractSocket::SocketError>();
|
||||
needToRegisterMetaType = false;
|
||||
}
|
||||
|
@ -153,18 +155,21 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
|||
m_from = from;
|
||||
m_rcpt = to;
|
||||
// Authentication
|
||||
if (pref->getMailNotificationSMTPAuth()) {
|
||||
if (pref->getMailNotificationSMTPAuth())
|
||||
{
|
||||
m_username = pref->getMailNotificationSMTPUsername();
|
||||
m_password = pref->getMailNotificationSMTPPassword();
|
||||
}
|
||||
|
||||
// Connect to SMTP server
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (pref->getMailNotificationSMTPSSL()) {
|
||||
if (pref->getMailNotificationSMTPSSL())
|
||||
{
|
||||
m_socket->connectToHostEncrypted(pref->getMailNotificationSMTP(), DEFAULT_PORT_SSL);
|
||||
m_useSsl = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
#endif
|
||||
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
m_useSsl = false;
|
||||
|
@ -178,7 +183,8 @@ void Smtp::readyRead()
|
|||
qDebug() << Q_FUNC_INFO;
|
||||
// SMTP is line-oriented
|
||||
m_buffer += m_socket->readAll();
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
const int pos = m_buffer.indexOf("\r\n");
|
||||
if (pos < 0) return; // Loop exit condition
|
||||
const QByteArray line = m_buffer.left(pos);
|
||||
|
@ -187,9 +193,11 @@ void Smtp::readyRead()
|
|||
// Extract response code
|
||||
const QByteArray code = line.left(3);
|
||||
|
||||
switch (m_state) {
|
||||
switch (m_state)
|
||||
{
|
||||
case Init:
|
||||
if (code[0] == '2') {
|
||||
if (code[0] == '2')
|
||||
{
|
||||
// The server may send a multiline greeting/INIT/220 response.
|
||||
// We wait until it finishes.
|
||||
if (line[3] != ' ')
|
||||
|
@ -197,7 +205,8 @@ void Smtp::readyRead()
|
|||
// Connection was successful
|
||||
ehlo();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
logError(QLatin1String("Connection failed, unrecognized reply: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
|
@ -209,11 +218,13 @@ void Smtp::readyRead()
|
|||
break;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
case StartTLSSent:
|
||||
if (code == "220") {
|
||||
if (code == "220")
|
||||
{
|
||||
m_socket->startClientEncryption();
|
||||
ehlo();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
authenticate();
|
||||
}
|
||||
break;
|
||||
|
@ -226,59 +237,69 @@ void Smtp::readyRead()
|
|||
break;
|
||||
case AuthSent:
|
||||
case Authenticated:
|
||||
if (code[0] == '2') {
|
||||
if (code[0] == '2')
|
||||
{
|
||||
qDebug() << "Sending <mail from>...";
|
||||
m_socket->write("mail from:<" + m_from.toLatin1() + ">\r\n");
|
||||
m_socket->flush();
|
||||
m_state = Rcpt;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Authentication failed!
|
||||
logError(QLatin1String("Authentication failed, msg: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
case Rcpt:
|
||||
if (code[0] == '2') {
|
||||
if (code[0] == '2')
|
||||
{
|
||||
m_socket->write("rcpt to:<" + m_rcpt.toLatin1() + ">\r\n");
|
||||
m_socket->flush();
|
||||
m_state = Data;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
logError(QLatin1String("<mail from> was rejected by server, msg: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
case Data:
|
||||
if (code[0] == '2') {
|
||||
if (code[0] == '2')
|
||||
{
|
||||
m_socket->write("data\r\n");
|
||||
m_socket->flush();
|
||||
m_state = Body;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
logError(QLatin1String("<Rcpt to> was rejected by server, msg: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
case Body:
|
||||
if (code[0] == '3') {
|
||||
if (code[0] == '3')
|
||||
{
|
||||
m_socket->write(m_message + "\r\n.\r\n");
|
||||
m_socket->flush();
|
||||
m_state = Quit;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
logError(QLatin1String("<data> was rejected by server, msg: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
case Quit:
|
||||
if (code[0] == '2') {
|
||||
if (code[0] == '2')
|
||||
{
|
||||
m_socket->write("QUIT\r\n");
|
||||
m_socket->flush();
|
||||
// here, we just close.
|
||||
m_state = Close;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
logError(QLatin1String("Message was rejected by the server, error: ") + line);
|
||||
m_state = Close;
|
||||
}
|
||||
|
@ -296,10 +317,13 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, cons
|
|||
QByteArray rv = "";
|
||||
QByteArray line = key.toLatin1() + ": ";
|
||||
if (!prefix.isEmpty()) line += prefix;
|
||||
if (!value.contains("=?") && latin1->canEncode(value)) {
|
||||
if (!value.contains("=?") && latin1->canEncode(value))
|
||||
{
|
||||
bool firstWord = true;
|
||||
for (const QByteArray &word : asConst(value.toLatin1().split(' '))) {
|
||||
if (line.size() > 78) {
|
||||
for (const QByteArray &word : asConst(value.toLatin1().split(' ')))
|
||||
{
|
||||
if (line.size() > 78)
|
||||
{
|
||||
rv = rv + line + "\r\n";
|
||||
line.clear();
|
||||
}
|
||||
|
@ -310,7 +334,8 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, cons
|
|||
firstWord = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// The text cannot be losslessly encoded as Latin-1. Therefore, we
|
||||
// must use base64 encoding.
|
||||
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 int ct = base64.length();
|
||||
line += "=?utf-8?b?";
|
||||
for (int i = 0; i < ct; i += 4) {
|
||||
/*if (line.length() > 72) {
|
||||
for (int i = 0; i < ct; i += 4)
|
||||
{
|
||||
/*if (line.length() > 72)
|
||||
{
|
||||
rv += line + "?\n\r";
|
||||
line = " =?utf-8?b?";
|
||||
}*/
|
||||
|
@ -348,14 +375,17 @@ void Smtp::helo()
|
|||
|
||||
void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const QString &line)
|
||||
{
|
||||
if (code != "250") {
|
||||
if (code != "250")
|
||||
{
|
||||
// Error
|
||||
if (m_state == EhloSent) {
|
||||
if (m_state == EhloSent)
|
||||
{
|
||||
// try to send HELO instead of EHLO
|
||||
qDebug() << "EHLO failed, trying HELO instead...";
|
||||
helo();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Both EHLO and HELO failed, chances are this is NOT
|
||||
// a SMTP server
|
||||
logError("Both EHLO and HELO failed, msg: " + line);
|
||||
|
@ -364,20 +394,24 @@ void Smtp::parseEhloResponse(const QByteArray &code, const bool continued, const
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_state != EhloGreetReceived) {
|
||||
if (!continued) {
|
||||
if (m_state != EhloGreetReceived)
|
||||
{
|
||||
if (!continued)
|
||||
{
|
||||
// greeting only, no extensions
|
||||
qDebug() << "No extension";
|
||||
m_state = EhloDone;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// greeting followed by extensions
|
||||
m_state = EhloGreetReceived;
|
||||
qDebug() << "EHLO greet received";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "Supported extension: " << 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_extensions.contains("STARTTLS") && m_useSsl) {
|
||||
if (m_extensions.contains("STARTTLS") && m_useSsl)
|
||||
{
|
||||
qDebug() << "STARTTLS";
|
||||
startTLS();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
authenticate();
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +436,8 @@ void Smtp::authenticate()
|
|||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
if (!m_extensions.contains("AUTH") ||
|
||||
m_username.isEmpty() || m_password.isEmpty()) {
|
||||
m_username.isEmpty() || m_password.isEmpty())
|
||||
{
|
||||
// Skip authentication
|
||||
qDebug() << "Skipping authentication...";
|
||||
m_state = Authenticated;
|
||||
|
@ -414,19 +451,23 @@ void Smtp::authenticate()
|
|||
// authentication modes are supported by
|
||||
// the server
|
||||
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...";
|
||||
authCramMD5();
|
||||
}
|
||||
else if (auth.contains("PLAIN")) {
|
||||
else if (auth.contains("PLAIN"))
|
||||
{
|
||||
qDebug() << "Using PLAIN authentication...";
|
||||
authPlain();
|
||||
}
|
||||
else if (auth.contains("LOGIN")) {
|
||||
else if (auth.contains("LOGIN"))
|
||||
{
|
||||
qDebug() << "Using LOGIN authentication...";
|
||||
authLogin();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Skip authentication
|
||||
logError("The SMTP server does not seem to support any of the authentications modes "
|
||||
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
|
||||
|
@ -453,13 +494,15 @@ void Smtp::startTLS()
|
|||
|
||||
void Smtp::authCramMD5(const QByteArray &challenge)
|
||||
{
|
||||
if (m_state != AuthRequestSent) {
|
||||
if (m_state != AuthRequestSent)
|
||||
{
|
||||
m_socket->write("auth cram-md5\r\n");
|
||||
m_socket->flush();
|
||||
m_authType = AuthCramMD5;
|
||||
m_state = AuthRequestSent;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
const QByteArray response = m_username.toLatin1() + ' '
|
||||
+ hmacMD5(m_password.toLatin1(), QByteArray::fromBase64(challenge)).toHex();
|
||||
m_socket->write(response.toBase64() + "\r\n");
|
||||
|
@ -470,7 +513,8 @@ void Smtp::authCramMD5(const QByteArray &challenge)
|
|||
|
||||
void Smtp::authPlain()
|
||||
{
|
||||
if (m_state != AuthRequestSent) {
|
||||
if (m_state != AuthRequestSent)
|
||||
{
|
||||
m_authType = AuthPlain;
|
||||
// Prepare Auth string
|
||||
QByteArray auth;
|
||||
|
@ -489,18 +533,21 @@ void Smtp::authPlain()
|
|||
|
||||
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->flush();
|
||||
m_authType = AuthLogin;
|
||||
m_state = AuthRequestSent;
|
||||
}
|
||||
else if (m_state == AuthRequestSent) {
|
||||
else if (m_state == AuthRequestSent)
|
||||
{
|
||||
m_socket->write(m_username.toLatin1().toBase64() + "\r\n");
|
||||
m_socket->flush();
|
||||
m_state = AuthUsernameSent;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_socket->write(m_password.toLatin1().toBase64() + "\r\n");
|
||||
m_socket->flush();
|
||||
m_state = AuthSent;
|
||||
|
|
|
@ -306,11 +306,13 @@ bool Preferences::WinStartup() const
|
|||
void Preferences::setWinStartup(const bool b)
|
||||
{
|
||||
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
|
||||
if (b) {
|
||||
if (b)
|
||||
{
|
||||
const QString binPath = '"' + Utils::Fs::toNativePath(qApp->applicationFilePath()) + '"';
|
||||
settings.setValue("qBittorrent", binPath);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
settings.remove("qBittorrent");
|
||||
}
|
||||
}
|
||||
|
@ -530,7 +532,8 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
|||
QVector<Utils::Net::Subnet> ret;
|
||||
ret.reserve(subnets.size());
|
||||
|
||||
for (const QString &rawSubnet : subnets) {
|
||||
for (const QString &rawSubnet : subnets)
|
||||
{
|
||||
bool ok = false;
|
||||
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok);
|
||||
if (ok)
|
||||
|
@ -977,7 +980,8 @@ void Preferences::setNeverCheckFileAssoc(const bool check)
|
|||
bool Preferences::isTorrentFileAssocSet()
|
||||
{
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
@ -1008,13 +1012,15 @@ void Preferences::setTorrentFileAssoc(const bool set)
|
|||
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
|
||||
|
||||
// .Torrent association
|
||||
if (set) {
|
||||
if (set)
|
||||
{
|
||||
const QString oldProgId = settings.value(".torrent/Default").toString();
|
||||
if (!oldProgId.isEmpty() && (oldProgId != "qBittorrent"))
|
||||
settings.setValue(".torrent/OpenWithProgids/" + oldProgId, "");
|
||||
settings.setValue(".torrent/Default", "qBittorrent");
|
||||
}
|
||||
else if (isTorrentFileAssocSet()) {
|
||||
else if (isTorrentFileAssocSet())
|
||||
{
|
||||
settings.setValue(".torrent/Default", "");
|
||||
}
|
||||
|
||||
|
@ -1026,7 +1032,8 @@ void Preferences::setMagnetLinkAssoc(const bool set)
|
|||
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
|
||||
|
||||
// Magnet association
|
||||
if (set) {
|
||||
if (set)
|
||||
{
|
||||
const QString commandStr = '"' + 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/open/command/Default", Utils::Fs::toNativePath(commandStr));
|
||||
}
|
||||
else if (isMagnetLinkAssocSet()) {
|
||||
else if (isMagnetLinkAssocSet())
|
||||
{
|
||||
settings.remove("magnet");
|
||||
}
|
||||
|
||||
|
@ -1056,9 +1064,11 @@ bool Preferences::isTorrentFileAssocSet()
|
|||
{
|
||||
bool isSet = false;
|
||||
const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
|
||||
if (torrentId != NULL) {
|
||||
if (torrentId != NULL)
|
||||
{
|
||||
const CFStringRef defaultHandlerId = LSCopyDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer);
|
||||
if (defaultHandlerId != NULL) {
|
||||
if (defaultHandlerId != NULL)
|
||||
{
|
||||
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
|
||||
CFRelease(defaultHandlerId);
|
||||
|
@ -1072,7 +1082,8 @@ bool Preferences::isMagnetLinkAssocSet()
|
|||
{
|
||||
bool isSet = false;
|
||||
const CFStringRef defaultHandlerId = LSCopyDefaultHandlerForURLScheme(magnetUrlScheme);
|
||||
if (defaultHandlerId != NULL) {
|
||||
if (defaultHandlerId != NULL)
|
||||
{
|
||||
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
isSet = CFStringCompare(myBundleId, defaultHandlerId, 0) == kCFCompareEqualTo;
|
||||
CFRelease(defaultHandlerId);
|
||||
|
@ -1085,7 +1096,8 @@ void Preferences::setTorrentFileAssoc()
|
|||
if (isTorrentFileAssocSet())
|
||||
return;
|
||||
const CFStringRef torrentId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, torrentExtension, NULL);
|
||||
if (torrentId != NULL) {
|
||||
if (torrentId != NULL)
|
||||
{
|
||||
const CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
LSSetDefaultRoleHandlerForContentType(torrentId, kLSRolesViewer, myBundleId);
|
||||
CFRelease(torrentId);
|
||||
|
|
|
@ -72,7 +72,8 @@ const Profile *Profile::instance()
|
|||
QString Profile::location(const SpecialFolder folder) const
|
||||
{
|
||||
QString result;
|
||||
switch (folder) {
|
||||
switch (folder)
|
||||
{
|
||||
case SpecialFolder::Cache:
|
||||
result = m_profileImpl->cacheLocation();
|
||||
break;
|
||||
|
|
|
@ -83,7 +83,8 @@ QString Private::DefaultProfile::dataLocation() const
|
|||
const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
|
||||
+ 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'",
|
||||
qUtf8Printable(legacyDir), qUtf8Printable(dataDir));
|
||||
|
||||
|
@ -178,7 +179,8 @@ QString Private::Converter::toPortablePath(const QString &path) const
|
|||
return path;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (QDir::isAbsolutePath(path)) {
|
||||
if (QDir::isAbsolutePath(path))
|
||||
{
|
||||
const QChar driveLeter = path[0].toUpper();
|
||||
const QChar baseDriveLetter = m_baseDir.path()[0].toUpper();
|
||||
const bool onSameDrive = (driveLeter.category() == QChar::Letter_Uppercase) && (driveLeter == baseDriveLetter);
|
||||
|
|
|
@ -126,7 +126,8 @@ QVariantHash Article::data() const
|
|||
|
||||
void Article::markAsRead()
|
||||
{
|
||||
if (!m_isRead) {
|
||||
if (!m_isRead)
|
||||
{
|
||||
m_isRead = true;
|
||||
m_data[KeyIsRead] = m_isRead;
|
||||
emit read(this);
|
||||
|
|
|
@ -82,7 +82,8 @@ namespace
|
|||
|
||||
const QJsonObject jsonObj {jsonDoc.object()};
|
||||
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()};
|
||||
if (!jsonVal.isObject())
|
||||
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
|
||||
|
@ -177,7 +178,8 @@ QList<AutoDownloadRule> AutoDownloader::rules() const
|
|||
|
||||
void AutoDownloader::insertRule(const AutoDownloadRule &rule)
|
||||
{
|
||||
if (!hasRule(rule.name())) {
|
||||
if (!hasRule(rule.name()))
|
||||
{
|
||||
// Insert new rule
|
||||
setRule_impl(rule);
|
||||
m_dirty = true;
|
||||
|
@ -185,7 +187,8 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
|
|||
emit ruleAdded(rule.name());
|
||||
resetProcessingQueue();
|
||||
}
|
||||
else if (ruleByName(rule.name()) != rule) {
|
||||
else if (ruleByName(rule.name()) != rule)
|
||||
{
|
||||
// Update existing rule
|
||||
setRule_impl(rule);
|
||||
m_dirty = true;
|
||||
|
@ -211,7 +214,8 @@ bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleN
|
|||
|
||||
void AutoDownloader::removeRule(const QString &ruleName)
|
||||
{
|
||||
if (m_rules.contains(ruleName)) {
|
||||
if (m_rules.contains(ruleName))
|
||||
{
|
||||
emit ruleAboutToBeRemoved(ruleName);
|
||||
m_rules.remove(ruleName);
|
||||
m_dirty = true;
|
||||
|
@ -221,7 +225,8 @@ void AutoDownloader::removeRule(const QString &ruleName)
|
|||
|
||||
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
|
||||
{
|
||||
switch (format) {
|
||||
switch (format)
|
||||
{
|
||||
case RulesFileFormat::Legacy:
|
||||
return exportRulesToLegacyFormat();
|
||||
default:
|
||||
|
@ -231,7 +236,8 @@ QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) c
|
|||
|
||||
void AutoDownloader::importRules(const QByteArray &data, const AutoDownloader::RulesFileFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
switch (format)
|
||||
{
|
||||
case RulesFileFormat::Legacy:
|
||||
importRulesFromLegacyFormat(data);
|
||||
break;
|
||||
|
@ -286,8 +292,10 @@ QStringList AutoDownloader::smartEpisodeFilters() const
|
|||
{
|
||||
const QVariant filtersSetting = SettingsStorage::instance()->loadValue(SettingsKey_SmartEpisodeFilter);
|
||||
|
||||
if (filtersSetting.isNull()) {
|
||||
QStringList filters = {
|
||||
if (filtersSetting.isNull())
|
||||
{
|
||||
QStringList filters =
|
||||
{
|
||||
"s(\\d+)e(\\d+)", // Format 1: s01e01
|
||||
"(\\d+)x(\\d+)", // Format 2: 01x01
|
||||
"(\\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)
|
||||
{
|
||||
for (AutoDownloadRule &rule : m_rules) {
|
||||
for (AutoDownloadRule &rule : m_rules)
|
||||
{
|
||||
if (!rule.isEnabled()) continue;
|
||||
if (!rule.feedURLs().contains(job->feedURL)) 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();
|
||||
BitTorrent::Session::instance()->addTorrent(torrentURL, params);
|
||||
|
||||
if (BitTorrent::MagnetUri(torrentURL).isValid()) {
|
||||
if (Feed *feed = Session::instance()->feedByURL(job->feedURL)) {
|
||||
if (BitTorrent::MagnetUri(torrentURL).isValid())
|
||||
{
|
||||
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
|
||||
{
|
||||
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
|
||||
article->markAsRead();
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// waiting for torrent file downloading
|
||||
m_waitingJobs.insert(torrentURL, job);
|
||||
}
|
||||
|
@ -423,12 +435,14 @@ void AutoDownloader::load()
|
|||
|
||||
void AutoDownloader::loadRules(const QByteArray &data)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
const auto rules = rulesFromJSON(data);
|
||||
for (const auto &rule : rules)
|
||||
setRule_impl(rule);
|
||||
}
|
||||
catch (const ParsingError &error) {
|
||||
catch (const ParsingError &error)
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS AutoDownloader rules. Reason: %1")
|
||||
.arg(error.message()), Log::CRITICAL);
|
||||
}
|
||||
|
@ -438,7 +452,8 @@ void AutoDownloader::loadRulesLegacy()
|
|||
{
|
||||
const SettingsPtr settings = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss"));
|
||||
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());
|
||||
if (!rule.name().isEmpty())
|
||||
insertRule(rule);
|
||||
|
@ -475,7 +490,8 @@ void AutoDownloader::resetProcessingQueue()
|
|||
m_processingQueue.clear();
|
||||
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())
|
||||
addJobForArticle(article);
|
||||
}
|
||||
|
@ -489,13 +505,16 @@ void AutoDownloader::startProcessing()
|
|||
|
||||
void AutoDownloader::setProcessingEnabled(const bool enabled)
|
||||
{
|
||||
if (m_processingEnabled != enabled) {
|
||||
if (m_processingEnabled != enabled)
|
||||
{
|
||||
m_processingEnabled = enabled;
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
|
||||
if (m_processingEnabled) {
|
||||
if (m_processingEnabled)
|
||||
{
|
||||
startProcessing();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_processingQueue.clear();
|
||||
disconnect(Session::instance()->rootFolder(), &Folder::newArticle, this, &AutoDownloader::handleNewArticle);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ namespace
|
|||
|
||||
QJsonValue triStateBoolToJsonValue(const TriStateBool triStateBool)
|
||||
{
|
||||
switch (static_cast<signed char>(triStateBool)) {
|
||||
switch (static_cast<signed char>(triStateBool))
|
||||
{
|
||||
case 0: return false;
|
||||
case 1: return true;
|
||||
default: return {};
|
||||
|
@ -73,7 +74,8 @@ namespace
|
|||
|
||||
TriStateBool addPausedLegacyToTriStateBool(const int val)
|
||||
{
|
||||
switch (val) {
|
||||
switch (val)
|
||||
{
|
||||
case 1: return TriStateBool::True; // always
|
||||
case 2: return TriStateBool::False; // never
|
||||
default: return TriStateBool::Undefined; // default
|
||||
|
@ -82,7 +84,8 @@ namespace
|
|||
|
||||
int triStateBoolToAddPausedLegacy(const TriStateBool triStateBool)
|
||||
{
|
||||
switch (static_cast<signed char>(triStateBool)) {
|
||||
switch (static_cast<signed char>(triStateBool))
|
||||
{
|
||||
case 0: return 2; // never
|
||||
case 1: return 1; // always
|
||||
default: return 0; // default
|
||||
|
@ -164,7 +167,8 @@ QString computeEpisodeName(const QString &article)
|
|||
return {};
|
||||
|
||||
QStringList ret;
|
||||
for (int i = 1; i <= match.lastCapturedIndex(); ++i) {
|
||||
for (int i = 1; i <= match.lastCapturedIndex(); ++i)
|
||||
{
|
||||
const QString cap = match.captured(i);
|
||||
|
||||
if (cap.isEmpty())
|
||||
|
@ -199,8 +203,10 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, cons
|
|||
Q_ASSERT(!expression.isEmpty());
|
||||
|
||||
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
||||
if (regex.pattern().isEmpty()) {
|
||||
regex = QRegularExpression {
|
||||
if (regex.pattern().isEmpty())
|
||||
{
|
||||
regex = QRegularExpression
|
||||
{
|
||||
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
|
||||
, QRegularExpression::CaseInsensitiveOption};
|
||||
}
|
||||
|
@ -212,12 +218,14 @@ bool AutoDownloadRule::matchesExpression(const QString &articleTitle, const QStr
|
|||
{
|
||||
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
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_dataPtr->useRegex) {
|
||||
if (m_dataPtr->useRegex)
|
||||
{
|
||||
const QRegularExpression reg(cachedRegex(expression));
|
||||
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.
|
||||
// Order of wildcard tokens is unimportant (if order is important, they should have used *).
|
||||
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)};
|
||||
if (!reg.match(articleTitle).hasMatch())
|
||||
return false;
|
||||
|
@ -279,7 +288,8 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
|
|||
const QStringList episodes {matcher.captured(2).split(';')};
|
||||
const int seasonOurs {season.toInt()};
|
||||
|
||||
for (QString episode : episodes) {
|
||||
for (QString episode : episodes)
|
||||
{
|
||||
if (episode.isEmpty())
|
||||
continue;
|
||||
|
||||
|
@ -287,7 +297,8 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
|
|||
while ((episode.size() > 1) && episode.startsWith('0'))
|
||||
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 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);
|
||||
bool matched = matcher.hasMatch();
|
||||
|
||||
if (!matched) {
|
||||
if (!matched)
|
||||
{
|
||||
matcher = cachedRegex(partialPattern2).match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
if (matched)
|
||||
{
|
||||
const int seasonTheirs {matcher.captured(1).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()};
|
||||
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
|
||||
return true;
|
||||
}
|
||||
else { // Normal range
|
||||
else
|
||||
{ // Normal range
|
||||
const QStringList range {episode.split('-')};
|
||||
Q_ASSERT(range.size() == 2);
|
||||
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)};
|
||||
if (cachedRegex(expStr).match(articleTitle).hasMatch())
|
||||
return true;
|
||||
|
@ -343,7 +359,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
|
|||
|
||||
// See if this episode has been downloaded before
|
||||
const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
|
||||
if (previouslyMatched) {
|
||||
if (previouslyMatched)
|
||||
{
|
||||
if (!AutoDownloader::instance()->downloadRepacks())
|
||||
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
|
||||
// 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("-PROPER"));
|
||||
}
|
||||
|
@ -378,7 +396,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
|
|||
bool AutoDownloadRule::matches(const QVariantHash &articleData) const
|
||||
{
|
||||
const QDateTime articleDate {articleData[Article::KeyDate].toDateTime()};
|
||||
if (ignoreDays() > 0) {
|
||||
if (ignoreDays() > 0)
|
||||
{
|
||||
if (lastMatch().isValid() && (articleDate < lastMatch().addDays(ignoreDays())))
|
||||
return false;
|
||||
}
|
||||
|
@ -404,7 +423,8 @@ bool AutoDownloadRule::accepts(const QVariantHash &articleData)
|
|||
setLastMatch(articleData[Article::KeyDate].toDateTime());
|
||||
|
||||
// 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->lastComputedEpisodes.clear();
|
||||
}
|
||||
|
@ -474,10 +494,12 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
|||
|
||||
const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched);
|
||||
QStringList previouslyMatched;
|
||||
if (previouslyMatchedVal.isString()) {
|
||||
if (previouslyMatchedVal.isString())
|
||||
{
|
||||
previouslyMatched << previouslyMatchedVal.toString();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
for (const QJsonValue &val : asConst(previouslyMatchedVal.toArray()))
|
||||
previouslyMatched << val.toString();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
// 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")};
|
||||
const QDir storageDir {m_session->dataFileStorage()->storageDir()};
|
||||
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName)))
|
||||
|
@ -106,8 +107,10 @@ QList<Article *> Feed::articles() const
|
|||
void Feed::markAsRead()
|
||||
{
|
||||
const int oldUnreadCount = m_unreadCount;
|
||||
for (Article *article : asConst(m_articles)) {
|
||||
if (!article->isRead()) {
|
||||
for (Article *article : asConst(m_articles))
|
||||
{
|
||||
if (!article->isRead())
|
||||
{
|
||||
article->disconnect(this);
|
||||
article->markAsRead();
|
||||
--m_unreadCount;
|
||||
|
@ -115,7 +118,8 @@ void Feed::markAsRead()
|
|||
}
|
||||
}
|
||||
|
||||
if (m_unreadCount != oldUnreadCount) {
|
||||
if (m_unreadCount != oldUnreadCount)
|
||||
{
|
||||
m_dirty = true;
|
||||
store();
|
||||
emit unreadCountChanged(this);
|
||||
|
@ -180,7 +184,8 @@ void Feed::handleMaxArticlesPerFeedChanged(const int n)
|
|||
|
||||
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);
|
||||
emit iconLoaded(this);
|
||||
}
|
||||
|
@ -195,13 +200,15 @@ void Feed::handleDownloadFinished(const Net::DownloadResult &result)
|
|||
{
|
||||
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.")
|
||||
.arg(result.url));
|
||||
// Parse the download RSS
|
||||
m_parser->parse(result.data);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_isLoading = false;
|
||||
m_hasError = true;
|
||||
|
||||
|
@ -216,13 +223,15 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
|||
{
|
||||
m_hasError = !result.error.isEmpty();
|
||||
|
||||
if (!result.title.isEmpty() && (title() != result.title)) {
|
||||
if (!result.title.isEmpty() && (title() != result.title))
|
||||
{
|
||||
m_title = result.title;
|
||||
m_dirty = true;
|
||||
emit titleChanged(this);
|
||||
}
|
||||
|
||||
if (!result.lastBuildDate.isEmpty()) {
|
||||
if (!result.lastBuildDate.isEmpty())
|
||||
{
|
||||
m_lastBuildDate = result.lastBuildDate;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
@ -234,7 +243,8 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
|||
const int newArticlesCount = updateArticles(result.articles);
|
||||
store();
|
||||
|
||||
if (m_hasError) {
|
||||
if (m_hasError)
|
||||
{
|
||||
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
|
||||
, Log::WARNING);
|
||||
}
|
||||
|
@ -249,16 +259,19 @@ void Feed::load()
|
|||
{
|
||||
QFile file(m_session->dataFileStorage()->storageDir().absoluteFilePath(m_dataFileName));
|
||||
|
||||
if (!file.exists()) {
|
||||
if (!file.exists())
|
||||
{
|
||||
loadArticlesLegacy();
|
||||
m_dirty = true;
|
||||
store(); // convert to new format
|
||||
}
|
||||
else if (file.open(QFile::ReadOnly)) {
|
||||
else if (file.open(QFile::ReadOnly))
|
||||
{
|
||||
loadArticles(file.readAll());
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
|
||||
.arg(m_dataFileName, file.errorString())
|
||||
, Log::WARNING);
|
||||
|
@ -269,28 +282,33 @@ void Feed::loadArticles(const QByteArray &data)
|
|||
{
|
||||
QJsonParseError 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())
|
||||
, Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.isArray()) {
|
||||
if (!jsonDoc.isArray())
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS Session data. Invalid data format."), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonArray jsonArr = jsonDoc.array();
|
||||
int i = -1;
|
||||
for (const QJsonValue &jsonVal : jsonArr) {
|
||||
for (const QJsonValue &jsonVal : jsonArr)
|
||||
{
|
||||
++i;
|
||||
if (!jsonVal.isObject()) {
|
||||
if (!jsonVal.isObject())
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i)
|
||||
, Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
auto article = new Article(this, jsonVal.toObject());
|
||||
if (!addArticle(article))
|
||||
delete article;
|
||||
|
@ -304,13 +322,15 @@ void Feed::loadArticlesLegacy()
|
|||
const SettingsPtr qBTRSSFeeds = Profile::instance()->applicationSettings(QStringLiteral("qBittorrent-rss-feeds"));
|
||||
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();
|
||||
// update legacy keys
|
||||
hash[Article::KeyLink] = hash.take(QLatin1String("news_link"));
|
||||
hash[Article::KeyTorrentURL] = hash.take(QLatin1String("torrent_url"));
|
||||
hash[Article::KeyIsRead] = hash.take(QLatin1String("read"));
|
||||
try {
|
||||
try
|
||||
{
|
||||
auto article = new Article(this, hash);
|
||||
if (!addArticle(article))
|
||||
delete article;
|
||||
|
@ -353,7 +373,8 @@ bool Feed::addArticle(Article *article)
|
|||
|
||||
m_articles[article->guid()] = article;
|
||||
m_articlesByDate.insert(lowerBound, article);
|
||||
if (!article->isRead()) {
|
||||
if (!article->isRead())
|
||||
{
|
||||
increaseUnreadCount();
|
||||
connect(article, &Article::read, this, &Feed::handleArticleRead);
|
||||
}
|
||||
|
@ -414,12 +435,14 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
|
|||
QDateTime dummyPubDate {QDateTime::currentDateTime()};
|
||||
QVector<QVariantHash> newArticles;
|
||||
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.
|
||||
// To prevent processing of "out-of-limit" articles we must not assign dates
|
||||
// that are earlier than the dates of existing articles.
|
||||
const Article *existingArticle = articleByGUID(article[Article::KeyId].toString());
|
||||
if (existingArticle) {
|
||||
if (existingArticle)
|
||||
{
|
||||
dummyPubDate = existingArticle->date().addMSecs(-1);
|
||||
continue;
|
||||
}
|
||||
|
@ -462,7 +485,8 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
|
|||
int newArticlesCount = 0;
|
||||
std::for_each(sortData.crbegin(), sortData.crend(), [this, &newArticlesCount](const ArticleSortAdaptor &a)
|
||||
{
|
||||
if (a.second) {
|
||||
if (a.second)
|
||||
{
|
||||
addArticle(new Article {this, *a.second});
|
||||
++newArticlesCount;
|
||||
}
|
||||
|
@ -482,7 +506,8 @@ QJsonValue Feed::toJsonValue(const bool withData) const
|
|||
jsonObj.insert(KEY_UID, uid().toString());
|
||||
jsonObj.insert(KEY_URL, url());
|
||||
|
||||
if (withData) {
|
||||
if (withData)
|
||||
{
|
||||
jsonObj.insert(KEY_TITLE, title());
|
||||
jsonObj.insert(KEY_LASTBUILDDATE, lastBuildDate());
|
||||
jsonObj.insert(KEY_ISLOADING, isLoading());
|
||||
|
@ -499,7 +524,8 @@ QJsonValue Feed::toJsonValue(const bool withData) const
|
|||
|
||||
void Feed::handleSessionProcessingEnabledChanged(const bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
if (enabled)
|
||||
{
|
||||
downloadIcon();
|
||||
disconnect(m_session, &Session::processingStateChanged
|
||||
, this, &Feed::handleSessionProcessingEnabledChanged);
|
||||
|
|
|
@ -57,7 +57,8 @@ QList<Article *> Folder::articles() const
|
|||
{
|
||||
QList<Article *> news;
|
||||
|
||||
for (Item *item : asConst(items())) {
|
||||
for (Item *item : asConst(items()))
|
||||
{
|
||||
int n = news.size();
|
||||
news << item->articles();
|
||||
std::inplace_merge(news.begin(), news.begin() + n, news.end()
|
||||
|
|
|
@ -47,7 +47,8 @@ Item::~Item() {}
|
|||
|
||||
void Item::setPath(const QString &path)
|
||||
{
|
||||
if (path != m_path) {
|
||||
if (path != m_path)
|
||||
{
|
||||
m_path = path;
|
||||
emit pathChanged(this);
|
||||
}
|
||||
|
@ -69,7 +70,8 @@ bool Item::isValidPath(const QString &path)
|
|||
QString(R"(\A[^\%1]+(\%1[^\%1]+)*\z)").arg(Item::PathSeparator)
|
||||
, QRegularExpression::DontCaptureOption);
|
||||
|
||||
if (path.isEmpty() || !re.match(path).hasMatch()) {
|
||||
if (path.isEmpty() || !re.match(path).hasMatch())
|
||||
{
|
||||
qDebug() << "Incorrect RSS Item path:" << path;
|
||||
return false;
|
||||
}
|
||||
|
@ -93,7 +95,8 @@ QStringList Item::expandPath(const QString &path)
|
|||
// return result;
|
||||
|
||||
int index = 0;
|
||||
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0) {
|
||||
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
|
||||
{
|
||||
result << path.left(index);
|
||||
++index;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ namespace
|
|||
// 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-special.ent
|
||||
static const QHash<QString, QString> HTMLEntities {
|
||||
static const QHash<QString, QString> HTMLEntities
|
||||
{
|
||||
{"nbsp", " "}, // no-break space = non-breaking space, U+00A0 ISOnum
|
||||
{"iexcl", "¡"}, // inverted exclamation mark, U+00A1 ISOnum
|
||||
{"cent", "¢"}, // cent sign, U+00A2 ISOnum
|
||||
|
@ -359,17 +360,20 @@ namespace
|
|||
// Ported to Qt from KDElibs4
|
||||
QDateTime parseDate(const QString &string)
|
||||
{
|
||||
const char shortDay[][4] = {
|
||||
const char shortDay[][4] =
|
||||
{
|
||||
"Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat",
|
||||
"Sun"
|
||||
};
|
||||
const char longDay[][10] = {
|
||||
const char longDay[][10] =
|
||||
{
|
||||
"Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday",
|
||||
"Sunday"
|
||||
};
|
||||
const char shortMonth[][4] = {
|
||||
const char shortMonth[][4] =
|
||||
{
|
||||
"Jan", "Feb", "Mar", "Apr",
|
||||
"May", "Jun", "Jul", "Aug",
|
||||
"Sep", "Oct", "Nov", "Dec"
|
||||
|
@ -389,7 +393,8 @@ namespace
|
|||
// 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+)$");
|
||||
QStringList parts;
|
||||
if (!str.indexOf(rx)) {
|
||||
if (!str.indexOf(rx))
|
||||
{
|
||||
// Check that if date has '-' separators, both separators are '-'.
|
||||
parts = rx.capturedTexts();
|
||||
const bool h1 = (parts[3] == QLatin1String("-"));
|
||||
|
@ -397,7 +402,8 @@ namespace
|
|||
if (h1 != h2)
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// 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)$");
|
||||
if (str.indexOf(rx))
|
||||
|
@ -421,7 +427,8 @@ namespace
|
|||
return QDateTime::currentDateTime();
|
||||
|
||||
int second = 0;
|
||||
if (!parts[nsec].isEmpty()) {
|
||||
if (!parts[nsec].isEmpty())
|
||||
{
|
||||
second = parts[nsec].toInt(&ok[0]);
|
||||
if (!ok[0])
|
||||
return QDateTime::currentDateTime();
|
||||
|
@ -433,7 +440,8 @@ namespace
|
|||
int month = 0;
|
||||
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
|
||||
int dayOfWeek = -1;
|
||||
if (!parts[nwday].isEmpty()) {
|
||||
if (!parts[nwday].isEmpty())
|
||||
{
|
||||
// Look up the weekday name
|
||||
while ((++dayOfWeek < 7) && (shortDay[dayOfWeek] != parts[nwday]));
|
||||
if (dayOfWeek >= 7)
|
||||
|
@ -444,7 +452,8 @@ namespace
|
|||
// || (dayOfWeek < 0 && format == RFCDateDay))
|
||||
// return QDateTime;
|
||||
const int i = parts[nyear].size();
|
||||
if (i < 4) {
|
||||
if (i < 4)
|
||||
{
|
||||
// It's an obsolete year specification with less than 4 digits
|
||||
year += ((i == 2) && (year < 50)) ? 2000 : 1900;
|
||||
}
|
||||
|
@ -452,9 +461,11 @@ namespace
|
|||
// Parse the UTC offset part
|
||||
int offset = 0; // set default to '-0000'
|
||||
bool negOffset = false;
|
||||
if (parts.count() > 10) {
|
||||
if (parts.count() > 10)
|
||||
{
|
||||
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
|
||||
if (!parts[10].indexOf(rx)) {
|
||||
if (!parts[10].indexOf(rx))
|
||||
{
|
||||
// It's a UTC offset ±hhmm
|
||||
parts = rx.capturedTexts();
|
||||
offset = parts[2].toInt(&ok[0]) * 3600;
|
||||
|
@ -466,13 +477,16 @@ namespace
|
|||
if (negOffset)
|
||||
offset = -offset;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Check for an obsolete time zone name
|
||||
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'
|
||||
}
|
||||
else if ((zone != "UT") && (zone != "GMT")) { // treated as '+0000'
|
||||
else if ((zone != "UT") && (zone != "GMT"))
|
||||
{ // treated as '+0000'
|
||||
offset = (zone == "EDT")
|
||||
? -4 * 3600
|
||||
: ((zone == "EST") || (zone == "CDT"))
|
||||
|
@ -484,7 +498,8 @@ namespace
|
|||
: (zone == "PST")
|
||||
? -8 * 3600
|
||||
: 0;
|
||||
if (!offset) {
|
||||
if (!offset)
|
||||
{
|
||||
// Check for any other alphabetic time zone
|
||||
bool nonalpha = false;
|
||||
for (int i = 0, end = zone.size(); (i < end) && !nonalpha; ++i)
|
||||
|
@ -509,7 +524,8 @@ namespace
|
|||
if (!result.isValid())
|
||||
return QDateTime::currentDateTime(); // invalid date/time
|
||||
|
||||
if (leapSecond) {
|
||||
if (leapSecond)
|
||||
{
|
||||
// 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.
|
||||
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);
|
||||
bool foundChannel = false;
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "rss") {
|
||||
while (xml.readNextStartElement())
|
||||
{
|
||||
if (xml.name() == "rss")
|
||||
{
|
||||
// Find channels
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "channel") {
|
||||
while (xml.readNextStartElement())
|
||||
{
|
||||
if (xml.name() == "channel")
|
||||
{
|
||||
parseRSSChannel(xml);
|
||||
foundChannel = true;
|
||||
break;
|
||||
|
@ -563,7 +583,8 @@ void Parser::parse_impl(const QByteArray &feedData)
|
|||
}
|
||||
break;
|
||||
}
|
||||
if (xml.name() == "feed") { // Atom feed
|
||||
if (xml.name() == "feed")
|
||||
{ // Atom feed
|
||||
parseAtomChannel(xml);
|
||||
foundChannel = true;
|
||||
break;
|
||||
|
@ -573,10 +594,12 @@ void Parser::parse_impl(const QByteArray &feedData)
|
|||
xml.skipCurrentElement();
|
||||
}
|
||||
|
||||
if (!foundChannel) {
|
||||
if (!foundChannel)
|
||||
{
|
||||
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).")
|
||||
.arg(xml.errorString()).arg(xml.lineNumber())
|
||||
.arg(xml.columnNumber()).arg(xml.characterOffset());
|
||||
|
@ -592,43 +615,53 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
|||
QVariantHash article;
|
||||
QString altTorrentUrl;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
xml.readNext();
|
||||
const QString name(xml.name().toString());
|
||||
|
||||
if (xml.isEndElement() && (name == QLatin1String("item")))
|
||||
break;
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (name == QLatin1String("title")) {
|
||||
if (xml.isStartElement())
|
||||
{
|
||||
if (name == QLatin1String("title"))
|
||||
{
|
||||
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"))
|
||||
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
|
||||
else if (xml.attributes().value("type").isEmpty())
|
||||
altTorrentUrl = xml.attributes().value(QLatin1String("url")).toString();
|
||||
}
|
||||
else if (name == QLatin1String("link")) {
|
||||
else if (name == QLatin1String("link"))
|
||||
{
|
||||
const QString text {xml.readElementText().trimmed()};
|
||||
if (text.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive))
|
||||
article[Article::KeyTorrentURL] = text; // magnet link instead of a news URL
|
||||
else
|
||||
article[Article::KeyLink] = text;
|
||||
}
|
||||
else if (name == QLatin1String("description")) {
|
||||
else if (name == QLatin1String("description"))
|
||||
{
|
||||
article[Article::KeyDescription] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
}
|
||||
else if (name == QLatin1String("pubDate")) {
|
||||
else if (name == QLatin1String("pubDate"))
|
||||
{
|
||||
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed());
|
||||
}
|
||||
else if (name == QLatin1String("author")) {
|
||||
else if (name == QLatin1String("author"))
|
||||
{
|
||||
article[Article::KeyAuthor] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (name == QLatin1String("guid")) {
|
||||
else if (name == QLatin1String("guid"))
|
||||
{
|
||||
article[Article::KeyId] = xml.readElementText().trimmed();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
}
|
||||
}
|
||||
|
@ -642,24 +675,31 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
|||
|
||||
void Parser::parseRSSChannel(QXmlStreamReader &xml)
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
xml.readNext();
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == QLatin1String("title")) {
|
||||
if (xml.isStartElement())
|
||||
{
|
||||
if (xml.name() == QLatin1String("title"))
|
||||
{
|
||||
m_result.title = xml.readElementText();
|
||||
}
|
||||
else if (xml.name() == QLatin1String("lastBuildDate")) {
|
||||
else if (xml.name() == QLatin1String("lastBuildDate"))
|
||||
{
|
||||
const QString lastBuildDate = xml.readElementText();
|
||||
if (!lastBuildDate.isEmpty()) {
|
||||
if (m_result.lastBuildDate == lastBuildDate) {
|
||||
if (!lastBuildDate.isEmpty())
|
||||
{
|
||||
if (m_result.lastBuildDate == lastBuildDate)
|
||||
{
|
||||
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
|
||||
return;
|
||||
}
|
||||
m_result.lastBuildDate = lastBuildDate;
|
||||
}
|
||||
}
|
||||
else if (xml.name() == QLatin1String("item")) {
|
||||
else if (xml.name() == QLatin1String("item"))
|
||||
{
|
||||
parseRssArticle(xml);
|
||||
}
|
||||
}
|
||||
|
@ -671,18 +711,22 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
|||
QVariantHash article;
|
||||
bool doubleContent = false;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
xml.readNext();
|
||||
const QString name(xml.name().toString());
|
||||
|
||||
if (xml.isEndElement() && (name == QLatin1String("entry")))
|
||||
break;
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (name == QLatin1String("title")) {
|
||||
if (xml.isStartElement())
|
||||
{
|
||||
if (name == QLatin1String("title"))
|
||||
{
|
||||
article[Article::KeyTitle] = xml.readElementText().trimmed();
|
||||
}
|
||||
else if (name == QLatin1String("link")) {
|
||||
else if (name == QLatin1String("link"))
|
||||
{
|
||||
const QString link = (xml.attributes().isEmpty()
|
||||
? xml.readElementText().trimmed()
|
||||
: 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);
|
||||
|
||||
}
|
||||
else if ((name == QLatin1String("summary")) || (name == QLatin1String("content"))) {
|
||||
if (doubleContent) { // Duplicate content -> ignore
|
||||
else if ((name == QLatin1String("summary")) || (name == QLatin1String("content")))
|
||||
{
|
||||
if (doubleContent)
|
||||
{ // Duplicate content -> ignore
|
||||
xml.skipCurrentElement();
|
||||
continue;
|
||||
}
|
||||
|
@ -705,28 +751,34 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
|||
// Try to also parse broken articles, which don't use html '&' escapes
|
||||
// Actually works great for non-broken content too
|
||||
const QString feedText = xml.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
|
||||
if (!feedText.isEmpty()) {
|
||||
if (!feedText.isEmpty())
|
||||
{
|
||||
article[Article::KeyDescription] = feedText;
|
||||
doubleContent = true;
|
||||
}
|
||||
}
|
||||
else if (name == QLatin1String("updated")) {
|
||||
else if (name == QLatin1String("updated"))
|
||||
{
|
||||
// ATOM uses standard compliant date, don't do fancy stuff
|
||||
const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
|
||||
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
|
||||
}
|
||||
else if (name == QLatin1String("author")) {
|
||||
while (xml.readNextStartElement()) {
|
||||
else if (name == QLatin1String("author"))
|
||||
{
|
||||
while (xml.readNextStartElement())
|
||||
{
|
||||
if (xml.name() == QLatin1String("name"))
|
||||
article[Article::KeyAuthor] = xml.readElementText().trimmed();
|
||||
else
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
else if (name == QLatin1String("id")) {
|
||||
else if (name == QLatin1String("id"))
|
||||
{
|
||||
article[Article::KeyId] = xml.readElementText().trimmed();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
article[name] = xml.readElementText(QXmlStreamReader::IncludeChildElements);
|
||||
}
|
||||
}
|
||||
|
@ -739,24 +791,31 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
|
|||
{
|
||||
m_baseUrl = xml.attributes().value("xml:base").toString();
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
xml.readNext();
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == QLatin1String("title")) {
|
||||
if (xml.isStartElement())
|
||||
{
|
||||
if (xml.name() == QLatin1String("title"))
|
||||
{
|
||||
m_result.title = xml.readElementText();
|
||||
}
|
||||
else if (xml.name() == QLatin1String("updated")) {
|
||||
else if (xml.name() == QLatin1String("updated"))
|
||||
{
|
||||
const QString lastBuildDate = xml.readElementText();
|
||||
if (!lastBuildDate.isEmpty()) {
|
||||
if (m_result.lastBuildDate == lastBuildDate) {
|
||||
if (!lastBuildDate.isEmpty())
|
||||
{
|
||||
if (m_result.lastBuildDate == lastBuildDate)
|
||||
{
|
||||
qDebug() << "The RSS feed has not changed since last time, aborting parsing.";
|
||||
return;
|
||||
}
|
||||
m_result.lastBuildDate = lastBuildDate;
|
||||
}
|
||||
}
|
||||
else if (xml.name() == QLatin1String("entry")) {
|
||||
else if (xml.name() == QLatin1String("entry"))
|
||||
{
|
||||
parseAtomArticle(xml);
|
||||
}
|
||||
}
|
||||
|
@ -776,14 +835,16 @@ void Parser::addArticle(QVariantHash article)
|
|||
if (localId.toString().isEmpty())
|
||||
localId = article.value(Article::KeyTitle);
|
||||
|
||||
if (localId.toString().isEmpty()) {
|
||||
if (localId.toString().isEmpty())
|
||||
{
|
||||
// The article could not be uniquely identified
|
||||
// since it has no appropriate data.
|
||||
// Just ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_articleIDs.contains(localId.toString())) {
|
||||
if (m_articleIDs.contains(localId.toString()))
|
||||
{
|
||||
// The article could not be uniquely identified
|
||||
// since the Feed has duplicate identifiers.
|
||||
// Just ignore it.
|
||||
|
|
|
@ -97,7 +97,8 @@ Session::Session()
|
|||
load();
|
||||
|
||||
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
|
||||
if (m_processingEnabled) {
|
||||
if (m_processingEnabled)
|
||||
{
|
||||
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
|
||||
refresh();
|
||||
}
|
||||
|
@ -155,7 +156,8 @@ bool Session::addFolder(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)
|
||||
*error = tr("RSS feed with given URL already exists: %1.").arg(url);
|
||||
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)
|
||||
{
|
||||
if (itemPath.isEmpty()) {
|
||||
if (itemPath.isEmpty())
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Cannot move root folder.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto item = m_itemsByPath.value(itemPath);
|
||||
if (!item) {
|
||||
if (!item)
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Item doesn't exist: %1.").arg(itemPath);
|
||||
return false;
|
||||
|
@ -200,7 +204,8 @@ bool Session::moveItem(Item *item, const QString &destPath, QString *error)
|
|||
return false;
|
||||
|
||||
auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
|
||||
if (srcFolder != destFolder) {
|
||||
if (srcFolder != destFolder)
|
||||
{
|
||||
srcFolder->removeItem(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)
|
||||
{
|
||||
if (itemPath.isEmpty()) {
|
||||
if (itemPath.isEmpty())
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Cannot delete root folder.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto item = m_itemsByPath.value(itemPath);
|
||||
if (!item) {
|
||||
if (!item)
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Item doesn't exist: %1.").arg(itemPath);
|
||||
return false;
|
||||
|
@ -248,12 +255,14 @@ Item *Session::itemByPath(const QString &path) const
|
|||
void Session::load()
|
||||
{
|
||||
QFile itemsFile(m_confFileStorage->storageDir().absoluteFilePath(FeedsFileName));
|
||||
if (!itemsFile.exists()) {
|
||||
if (!itemsFile.exists())
|
||||
{
|
||||
loadLegacy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itemsFile.open(QFile::ReadOnly)) {
|
||||
if (!itemsFile.open(QFile::ReadOnly))
|
||||
{
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't read RSS Session data from %1. Error: %2")
|
||||
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
|
||||
|
@ -262,14 +271,16 @@ void Session::load()
|
|||
|
||||
QJsonParseError jsonError;
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
{
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't parse RSS Session data from %1. Error: %2")
|
||||
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.isObject()) {
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS Session data from %1. Invalid data format.")
|
||||
.arg(itemsFile.fileName()), Log::WARNING);
|
||||
|
@ -282,9 +293,11 @@ void Session::load()
|
|||
void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
{
|
||||
bool updated = false;
|
||||
for (const QString &key : asConst(jsonObj.keys())) {
|
||||
for (const QString &key : asConst(jsonObj.keys()))
|
||||
{
|
||||
const QJsonValue val {jsonObj[key]};
|
||||
if (val.isString()) {
|
||||
if (val.isString())
|
||||
{
|
||||
// previous format (reduced form) doesn't contain UID
|
||||
QString url = val.toString();
|
||||
if (url.isEmpty())
|
||||
|
@ -292,31 +305,38 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
|||
addFeedToFolder(generateUID(), url, key, folder);
|
||||
updated = true;
|
||||
}
|
||||
else if (val.isObject()) {
|
||||
else if (val.isObject())
|
||||
{
|
||||
const QJsonObject valObj {val.toObject()};
|
||||
if (valObj.contains("url")) {
|
||||
if (!valObj["url"].isString()) {
|
||||
if (valObj.contains("url"))
|
||||
{
|
||||
if (!valObj["url"].isString())
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS Feed '%1'. URL is required.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
QUuid uid;
|
||||
if (valObj.contains("uid")) {
|
||||
if (valObj.contains("uid"))
|
||||
{
|
||||
uid = QUuid {valObj["uid"].toString()};
|
||||
if (uid.isNull()) {
|
||||
if (uid.isNull())
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS Feed '%1'. UID is invalid.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_feedsByUID.contains(uid)) {
|
||||
if (m_feedsByUID.contains(uid))
|
||||
{
|
||||
LogMsg(tr("Duplicate RSS Feed UID: %1. Configuration seems to be corrupted.")
|
||||
.arg(uid.toString()), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// previous format doesn't contain UID
|
||||
uid = generateUID();
|
||||
updated = true;
|
||||
|
@ -324,11 +344,13 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
|||
|
||||
addFeedToFolder(uid, valObj["url"].toString(), key, folder);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
loadFolder(valObj, addSubfolder(key, folder));
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't load RSS Item '%1'. Invalid data format.")
|
||||
.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 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);
|
||||
return;
|
||||
}
|
||||
|
||||
uint i = 0;
|
||||
for (QString legacyPath : legacyFeedPaths) {
|
||||
for (QString legacyPath : legacyFeedPaths)
|
||||
{
|
||||
if (Item::PathSeparator == QString(legacyPath[0]))
|
||||
legacyPath.remove(0, 1);
|
||||
const QString parentFolderPath = Item::parentPath(legacyPath);
|
||||
|
@ -374,13 +398,15 @@ void Session::store()
|
|||
|
||||
Folder *Session::prepareItemDest(const QString &path, QString *error)
|
||||
{
|
||||
if (!Item::isValidPath(path)) {
|
||||
if (!Item::isValidPath(path))
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Incorrect RSS Item path: %1.").arg(path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_itemsByPath.contains(path)) {
|
||||
if (m_itemsByPath.contains(path))
|
||||
{
|
||||
if (error)
|
||||
*error = tr("RSS item with given path already exists: %1.").arg(path);
|
||||
return nullptr;
|
||||
|
@ -388,7 +414,8 @@ Folder *Session::prepareItemDest(const QString &path, QString *error)
|
|||
|
||||
const QString destFolderPath = Item::parentPath(path);
|
||||
auto destFolder = qobject_cast<Folder *>(m_itemsByPath.value(destFolderPath));
|
||||
if (!destFolder) {
|
||||
if (!destFolder)
|
||||
{
|
||||
if (error)
|
||||
*error = tr("Parent folder doesn't exist: %1.").arg(destFolderPath);
|
||||
return nullptr;
|
||||
|
@ -413,7 +440,8 @@ Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QStri
|
|||
|
||||
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::iconLoaded, this, &Session::feedIconLoaded);
|
||||
connect(feed, &Feed::stateChanged, this, &Session::feedStateChanged);
|
||||
|
@ -435,14 +463,17 @@ bool Session::isProcessingEnabled() const
|
|||
|
||||
void Session::setProcessingEnabled(bool enabled)
|
||||
{
|
||||
if (m_processingEnabled != enabled) {
|
||||
if (m_processingEnabled != enabled)
|
||||
{
|
||||
m_processingEnabled = enabled;
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_ProcessingEnabled, m_processingEnabled);
|
||||
if (m_processingEnabled) {
|
||||
if (m_processingEnabled)
|
||||
{
|
||||
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
|
||||
refresh();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_refreshTimer.stop();
|
||||
}
|
||||
|
||||
|
@ -482,7 +513,8 @@ int Session::refreshInterval() const
|
|||
|
||||
void Session::setRefreshInterval(const int refreshInterval)
|
||||
{
|
||||
if (m_refreshInterval != refreshInterval) {
|
||||
if (m_refreshInterval != refreshInterval)
|
||||
{
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_RefreshInterval, refreshInterval);
|
||||
m_refreshInterval = refreshInterval;
|
||||
m_refreshTimer.start(m_refreshInterval * MsecsPerMin);
|
||||
|
@ -498,7 +530,8 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
|
|||
{
|
||||
m_itemsByPath.remove(item->path());
|
||||
auto feed = qobject_cast<Feed *>(item);
|
||||
if (feed) {
|
||||
if (feed)
|
||||
{
|
||||
m_feedsByUID.remove(feed->uid());
|
||||
m_feedsByURL.remove(feed->url());
|
||||
}
|
||||
|
@ -528,7 +561,8 @@ int Session::maxArticlesPerFeed() const
|
|||
|
||||
void Session::setMaxArticlesPerFeed(const int n)
|
||||
{
|
||||
if (m_maxArticlesPerFeed != n) {
|
||||
if (m_maxArticlesPerFeed != n)
|
||||
{
|
||||
m_maxArticlesPerFeed = n;
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_MaxArticlesPerFeed, n);
|
||||
emit maxArticlesPerFeedChanged(n);
|
||||
|
|
|
@ -34,20 +34,26 @@
|
|||
* RSS Session configuration file format (JSON):
|
||||
*
|
||||
* =============== BEGIN ===============
|
||||
* {
|
||||
* "folder1": {
|
||||
* "subfolder1": {
|
||||
* "Feed name 1 (Alias)": {
|
||||
*
|
||||
{
|
||||
* "folder1":
|
||||
{
|
||||
* "subfolder1":
|
||||
{
|
||||
* "Feed name 1 (Alias)":
|
||||
{
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url1"
|
||||
* }
|
||||
* "Feed name 2 (Alias)": {
|
||||
* "Feed name 2 (Alias)":
|
||||
{
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url2"
|
||||
* }
|
||||
* },
|
||||
* "subfolder2": {},
|
||||
* "Feed name 3 (Alias)": {
|
||||
* "Feed name 3 (Alias)":
|
||||
{
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url3"
|
||||
* }
|
||||
|
|
|
@ -106,17 +106,21 @@ QVariant ScanFoldersModel::data(const QModelIndex &index, int role) const
|
|||
const PathData *pathData = m_pathList.at(index.row());
|
||||
QVariant value;
|
||||
|
||||
switch (index.column()) {
|
||||
switch (index.column())
|
||||
{
|
||||
case WATCH:
|
||||
if (role == Qt::DisplayRole)
|
||||
value = Utils::Fs::toNativePath(pathData->watchPath);
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
if (role == Qt::UserRole) {
|
||||
if (role == Qt::UserRole)
|
||||
{
|
||||
value = pathData->downloadType;
|
||||
}
|
||||
else if (role == Qt::DisplayRole) {
|
||||
switch (pathData->downloadType) {
|
||||
else if (role == Qt::DisplayRole)
|
||||
{
|
||||
switch (pathData->downloadType)
|
||||
{
|
||||
case DOWNLOAD_IN_WATCH_FOLDER:
|
||||
case DEFAULT_LOCATION:
|
||||
value = pathTypeDisplayName(pathData->downloadType);
|
||||
|
@ -139,7 +143,8 @@ QVariant ScanFoldersModel::headerData(int section, Qt::Orientation orientation,
|
|||
|
||||
QVariant title;
|
||||
|
||||
switch (section) {
|
||||
switch (section)
|
||||
{
|
||||
case WATCH:
|
||||
title = tr("Monitored Folder");
|
||||
break;
|
||||
|
@ -158,7 +163,8 @@ Qt::ItemFlags ScanFoldersModel::flags(const QModelIndex &index) const
|
|||
|
||||
Qt::ItemFlags flags;
|
||||
|
||||
switch (index.column()) {
|
||||
switch (index.column())
|
||||
{
|
||||
case WATCH:
|
||||
flags = QAbstractListModel::flags(index);
|
||||
break;
|
||||
|
@ -176,7 +182,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
|
|||
|| (index.column() != DOWNLOAD))
|
||||
return false;
|
||||
|
||||
if (role == Qt::UserRole) {
|
||||
if (role == Qt::UserRole)
|
||||
{
|
||||
const auto type = static_cast<PathType>(value.toInt());
|
||||
if (type == CUSTOM_LOCATION)
|
||||
return false;
|
||||
|
@ -185,7 +192,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
|
|||
m_pathList[index.row()]->downloadPath.clear();
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
else if (role == Qt::DisplayRole) {
|
||||
else if (role == Qt::DisplayRole)
|
||||
{
|
||||
const QString path = value.toString();
|
||||
if (path.isEmpty()) // means we didn't pass CUSTOM_LOCATION type
|
||||
return false;
|
||||
|
@ -194,7 +202,8 @@ bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value,
|
|||
m_pathList[index.row()]->downloadPath = Utils::Fs::toNativePath(path);
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -213,7 +222,8 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
|||
const QDir downloadDir(downloadPath);
|
||||
const QString canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
|
||||
if (!m_fsWatcher) {
|
||||
if (!m_fsWatcher)
|
||||
{
|
||||
m_fsWatcher = new FileSystemWatcher(this);
|
||||
connect(m_fsWatcher, &FileSystemWatcher::torrentsAdded, this, &ScanFoldersModel::addTorrentsToSession);
|
||||
}
|
||||
|
@ -249,7 +259,8 @@ void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
|
|||
if (!m_fsWatcher)
|
||||
return; // addPath() wasn't called before this
|
||||
|
||||
for (const QString &path : watchPaths) {
|
||||
for (const QString &path : watchPaths)
|
||||
{
|
||||
const QDir watchDir(path);
|
||||
const QString canonicalWatchPath = watchDir.canonicalPath();
|
||||
m_fsWatcher->addPath(canonicalWatchPath);
|
||||
|
@ -321,7 +332,8 @@ void ScanFoldersModel::makePersistent()
|
|||
{
|
||||
QVariantHash dirs;
|
||||
|
||||
for (const PathData *pathData : asConst(m_pathList)) {
|
||||
for (const PathData *pathData : asConst(m_pathList))
|
||||
{
|
||||
if (pathData->downloadType == CUSTOM_LOCATION)
|
||||
dirs.insert(Utils::Fs::toUniformPath(pathData->watchPath), Utils::Fs::toUniformPath(pathData->downloadPath));
|
||||
else
|
||||
|
@ -335,7 +347,8 @@ void ScanFoldersModel::configure()
|
|||
{
|
||||
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)
|
||||
addPath(i.key(), static_cast<PathType>(i.value().toInt()), QString());
|
||||
else
|
||||
|
@ -345,22 +358,27 @@ void ScanFoldersModel::configure()
|
|||
|
||||
void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
||||
{
|
||||
for (const QString &file : pathList) {
|
||||
for (const QString &file : pathList)
|
||||
{
|
||||
qDebug("File %s added", qUtf8Printable(file));
|
||||
|
||||
BitTorrent::AddTorrentParams params;
|
||||
if (downloadInWatchFolder(file)) {
|
||||
if (downloadInWatchFolder(file))
|
||||
{
|
||||
params.savePath = QFileInfo(file).dir().path();
|
||||
params.useAutoTMM = TriStateBool::False;
|
||||
}
|
||||
else if (!downloadInDefaultFolder(file)) {
|
||||
else if (!downloadInDefaultFolder(file))
|
||||
{
|
||||
params.savePath = downloadPathTorrentFolder(file);
|
||||
params.useAutoTMM = TriStateBool::False;
|
||||
}
|
||||
|
||||
if (file.endsWith(".magnet", Qt::CaseInsensitive)) {
|
||||
if (file.endsWith(".magnet", Qt::CaseInsensitive))
|
||||
{
|
||||
QFile f(file);
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
if (f.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream str(&f);
|
||||
while (!str.atEnd())
|
||||
BitTorrent::Session::instance()->addTorrent(str.readLine(), params);
|
||||
|
@ -368,17 +386,21 @@ void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
|||
f.close();
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("Failed to open magnet file: %s", qUtf8Printable(f.errorString()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
const BitTorrent::TorrentInfo torrentInfo = BitTorrent::TorrentInfo::loadFromFile(file);
|
||||
if (torrentInfo.isValid()) {
|
||||
if (torrentInfo.isValid())
|
||||
{
|
||||
BitTorrent::Session::instance()->addTorrent(torrentInfo, params);
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("Ignoring incomplete torrent file: %s", qUtf8Printable(file));
|
||||
}
|
||||
}
|
||||
|
@ -387,7 +409,8 @@ void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
|||
|
||||
QString ScanFoldersModel::pathTypeDisplayName(const PathType type)
|
||||
{
|
||||
switch (type) {
|
||||
switch (type)
|
||||
{
|
||||
case DOWNLOAD_IN_WATCH_FOLDER:
|
||||
return tr("Monitored folder");
|
||||
case DEFAULT_LOCATION:
|
||||
|
|
|
@ -42,7 +42,8 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
|
|||
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
|
||||
, this, &SearchDownloadHandler::downloadProcessFinished);
|
||||
const QStringList params {
|
||||
const QStringList params
|
||||
{
|
||||
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"),
|
||||
siteUrl,
|
||||
url
|
||||
|
@ -55,7 +56,8 @@ void SearchDownloadHandler::downloadProcessFinished(int exitcode)
|
|||
{
|
||||
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 QVector<QStringRef> parts = line.splitRef(' ');
|
||||
if (parts.size() == 2)
|
||||
|
|
|
@ -64,7 +64,8 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
|
|||
// Load environment variables (proxy)
|
||||
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
|
||||
const QStringList params {
|
||||
const QStringList params
|
||||
{
|
||||
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
|
||||
m_usedPlugins.join(','),
|
||||
m_category
|
||||
|
@ -137,13 +138,15 @@ void SearchHandler::readSearchOutput()
|
|||
QVector<SearchResult> searchResultList;
|
||||
searchResultList.reserve(lines.size());
|
||||
|
||||
for (const QByteArray &line : asConst(lines)) {
|
||||
for (const QByteArray &line : asConst(lines))
|
||||
{
|
||||
SearchResult searchResult;
|
||||
if (parseSearchResult(QString::fromUtf8(line), searchResult))
|
||||
searchResultList << searchResult;
|
||||
}
|
||||
|
||||
if (!searchResultList.isEmpty()) {
|
||||
if (!searchResultList.isEmpty())
|
||||
{
|
||||
for (const SearchResult &result : searchResultList)
|
||||
m_results.append(result);
|
||||
emit newSearchResults(searchResultList);
|
||||
|
|
|
@ -61,16 +61,19 @@ namespace
|
|||
while (iter.hasNext())
|
||||
dirs += iter.next();
|
||||
|
||||
for (const QString &dir : asConst(dirs)) {
|
||||
for (const QString &dir : asConst(dirs))
|
||||
{
|
||||
// python 3: remove "__pycache__" folders
|
||||
if (dir.endsWith("/__pycache__")) {
|
||||
if (dir.endsWith("/__pycache__"))
|
||||
{
|
||||
Utils::Fs::removeDirRecursive(dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
// python 2: remove "*.pyc" files
|
||||
const QStringList files = QDir(dir).entryList(QDir::Files);
|
||||
for (const QString &file : files) {
|
||||
for (const QString &file : files)
|
||||
{
|
||||
if (file.endsWith(".pyc"))
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
|
@ -115,7 +118,8 @@ QStringList SearchPluginManager::allPlugins() const
|
|||
QStringList SearchPluginManager::enabledPlugins() const
|
||||
{
|
||||
QStringList plugins;
|
||||
for (const PluginInfo *plugin : asConst(m_plugins)) {
|
||||
for (const PluginInfo *plugin : asConst(m_plugins))
|
||||
{
|
||||
if (plugin->enabled)
|
||||
plugins << plugin->name;
|
||||
}
|
||||
|
@ -126,9 +130,12 @@ QStringList SearchPluginManager::enabledPlugins() const
|
|||
QStringList SearchPluginManager::supportedCategories() const
|
||||
{
|
||||
QStringList result;
|
||||
for (const PluginInfo *plugin : asConst(m_plugins)) {
|
||||
if (plugin->enabled) {
|
||||
for (const QString &cat : plugin->supportedCategories) {
|
||||
for (const PluginInfo *plugin : asConst(m_plugins))
|
||||
{
|
||||
if (plugin->enabled)
|
||||
{
|
||||
for (const QString &cat : plugin->supportedCategories)
|
||||
{
|
||||
if (!result.contains(cat))
|
||||
result << cat;
|
||||
}
|
||||
|
@ -149,7 +156,8 @@ QStringList SearchPluginManager::getPluginCategories(const QString &pluginName)
|
|||
plugins << pluginName.trimmed();
|
||||
|
||||
QSet<QString> categories;
|
||||
for (const QString &name : asConst(plugins)) {
|
||||
for (const QString &name : asConst(plugins))
|
||||
{
|
||||
const PluginInfo *plugin = pluginInfo(name);
|
||||
if (!plugin) continue; // plugin wasn't found
|
||||
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)
|
||||
{
|
||||
PluginInfo *plugin = m_plugins.value(name, nullptr);
|
||||
if (plugin) {
|
||||
if (plugin)
|
||||
{
|
||||
plugin->enabled = enabled;
|
||||
// Save to Hard disk
|
||||
Preferences *const pref = Preferences::instance();
|
||||
|
@ -193,12 +202,14 @@ void SearchPluginManager::installPlugin(const QString &source)
|
|||
{
|
||||
clearPythonCache(engineLocation());
|
||||
|
||||
if (Net::DownloadManager::hasSupportedScheme(source)) {
|
||||
if (Net::DownloadManager::hasSupportedScheme(source))
|
||||
{
|
||||
using namespace Net;
|
||||
DownloadManager::instance()->download(DownloadRequest(source).saveToFile(true)
|
||||
, this, &SearchPluginManager::pluginDownloadFinished);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QString path = source;
|
||||
if (path.startsWith("file:", Qt::CaseInsensitive))
|
||||
path = QUrl(path).toLocalFile();
|
||||
|
@ -217,7 +228,8 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
|||
{
|
||||
const PluginVersion newVersion = getPluginVersion(path);
|
||||
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);
|
||||
emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed."));
|
||||
return;
|
||||
|
@ -226,7 +238,8 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
|||
// Process with install
|
||||
const QString destPath = pluginPath(name);
|
||||
bool updated = false;
|
||||
if (QFile::exists(destPath)) {
|
||||
if (QFile::exists(destPath))
|
||||
{
|
||||
// Backup in case install fails
|
||||
QFile::copy(destPath, destPath + ".bak");
|
||||
Utils::Fs::forceRemove(destPath);
|
||||
|
@ -237,11 +250,13 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
|||
// Update supported plugins
|
||||
update();
|
||||
// Check if this was correctly installed
|
||||
if (!m_plugins.contains(name)) {
|
||||
if (!m_plugins.contains(name))
|
||||
{
|
||||
// Remove broken file
|
||||
Utils::Fs::forceRemove(destPath);
|
||||
LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO);
|
||||
if (updated) {
|
||||
if (updated)
|
||||
{
|
||||
// restore backup
|
||||
QFile::copy(destPath + ".bak", destPath);
|
||||
Utils::Fs::forceRemove(destPath + ".bak");
|
||||
|
@ -249,13 +264,16 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
|||
update();
|
||||
emit pluginUpdateFailed(name, tr("Plugin is not supported."));
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
emit pluginInstallationFailed(name, tr("Plugin is not supported."));
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Install was successful, remove backup
|
||||
if (updated) {
|
||||
if (updated)
|
||||
{
|
||||
LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO);
|
||||
Utils::Fs::forceRemove(destPath + ".bak");
|
||||
}
|
||||
|
@ -284,10 +302,12 @@ void SearchPluginManager::updateIconPath(PluginInfo *const plugin)
|
|||
{
|
||||
if (!plugin) return;
|
||||
QString iconPath = QString::fromLatin1("%1/%2.png").arg(pluginsLocation(), plugin->name);
|
||||
if (QFile::exists(iconPath)) {
|
||||
if (QFile::exists(iconPath))
|
||||
{
|
||||
plugin->iconPath = iconPath;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
iconPath = QString::fromLatin1("%1/%2.ico").arg(pluginsLocation(), plugin->name);
|
||||
if (QFile::exists(iconPath))
|
||||
plugin->iconPath = iconPath;
|
||||
|
@ -317,7 +337,8 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
|
|||
|
||||
QString SearchPluginManager::categoryFullName(const QString &categoryName)
|
||||
{
|
||||
const QHash<QString, QString> categoryTable {
|
||||
const QHash<QString, QString> categoryTable
|
||||
{
|
||||
{"all", tr("All categories")},
|
||||
{"movies", tr("Movies")},
|
||||
{"tv", tr("TV shows")},
|
||||
|
@ -344,7 +365,8 @@ QString SearchPluginManager::pluginsLocation()
|
|||
QString SearchPluginManager::engineLocation()
|
||||
{
|
||||
static QString location;
|
||||
if (location.isEmpty()) {
|
||||
if (location.isEmpty())
|
||||
{
|
||||
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "nova3");
|
||||
|
||||
const QDir locationDir(location);
|
||||
|
@ -364,7 +386,8 @@ void SearchPluginManager::versionInfoDownloadFinished(const Net::DownloadResult
|
|||
|
||||
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);
|
||||
|
||||
QString pluginName = Utils::Fs::fileName(result.url);
|
||||
|
@ -372,7 +395,8 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
|
|||
installPlugin_impl(pluginName, filePath);
|
||||
Utils::Fs::forceRemove(filePath);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
const QString url = result.url;
|
||||
QString pluginName = url.mid(url.lastIndexOf('/') + 1);
|
||||
pluginName.replace(".py", "", Qt::CaseInsensitive);
|
||||
|
@ -432,21 +456,25 @@ void SearchPluginManager::update()
|
|||
|
||||
const QString capabilities = nova.readAll();
|
||||
QDomDocument xmlDoc;
|
||||
if (!xmlDoc.setContent(capabilities)) {
|
||||
if (!xmlDoc.setContent(capabilities))
|
||||
{
|
||||
qWarning() << "Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
|
||||
qWarning() << "Error: " << nova.readAllStandardError().constData();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
if (!engineElem.isNull()) {
|
||||
if (!engineElem.isNull())
|
||||
{
|
||||
const QString pluginName = engineElem.tagName();
|
||||
|
||||
auto plugin = std::make_unique<PluginInfo>();
|
||||
|
@ -456,7 +484,8 @@ void SearchPluginManager::update()
|
|||
plugin->url = engineElem.elementsByTagName("url").at(0).toElement().text();
|
||||
|
||||
const QStringList categories = engineElem.elementsByTagName("categories").at(0).toElement().text().split(' ');
|
||||
for (QString cat : categories) {
|
||||
for (QString cat : categories)
|
||||
{
|
||||
cat = cat.trimmed();
|
||||
if (!cat.isEmpty())
|
||||
plugin->supportedCategories << cat;
|
||||
|
@ -467,11 +496,13 @@ void SearchPluginManager::update()
|
|||
|
||||
updateIconPath(plugin.get());
|
||||
|
||||
if (!m_plugins.contains(pluginName)) {
|
||||
if (!m_plugins.contains(pluginName))
|
||||
{
|
||||
m_plugins[pluginName] = plugin.release();
|
||||
emit pluginInstalled(pluginName);
|
||||
}
|
||||
else if (m_plugins[pluginName]->version != plugin->version) {
|
||||
else if (m_plugins[pluginName]->version != plugin->version)
|
||||
{
|
||||
delete m_plugins.take(pluginName);
|
||||
m_plugins[pluginName] = plugin.release();
|
||||
emit pluginUpdated(pluginName);
|
||||
|
@ -486,7 +517,8 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
|
|||
int numCorrectData = 0;
|
||||
|
||||
const QVector<QByteArray> lines = Utils::ByteArray::splitToViews(info, "\n", QString::SkipEmptyParts);
|
||||
for (QByteArray line : lines) {
|
||||
for (QByteArray line : lines)
|
||||
{
|
||||
line = line.trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
if (line.startsWith('#')) continue;
|
||||
|
@ -500,17 +532,20 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
|
|||
if (!version.isValid()) continue;
|
||||
|
||||
++numCorrectData;
|
||||
if (isUpdateNeeded(pluginName, version)) {
|
||||
if (isUpdateNeeded(pluginName, version))
|
||||
{
|
||||
LogMsg(tr("Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version), Log::INFO);
|
||||
updateInfo[pluginName] = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (numCorrectData < lines.size()) {
|
||||
if (numCorrectData < lines.size())
|
||||
{
|
||||
emit checkForUpdatesFailed(tr("Incorrect update info received for %1 out of %2 plugins.")
|
||||
.arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size())));
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
emit checkForUpdatesFinished(updateInfo);
|
||||
}
|
||||
}
|
||||
|
@ -535,7 +570,8 @@ PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath)
|
|||
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return {};
|
||||
|
||||
while (!pluginFile.atEnd()) {
|
||||
while (!pluginFile.atEnd())
|
||||
{
|
||||
const QString line = QString(pluginFile.readLine()).remove(' ');
|
||||
if (!line.startsWith("#VERSION:", Qt::CaseInsensitive)) continue;
|
||||
|
||||
|
|
|
@ -67,7 +67,8 @@ namespace
|
|||
|
||||
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/DefaultSavePath", "Preferences/Downloads/SavePath"},
|
||||
{"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
|
||||
|
||||
const TransactionalSettings settings(QLatin1String("qBittorrent"));
|
||||
if (!settings.write(m_data)) {
|
||||
if (!settings.write(m_data))
|
||||
{
|
||||
m_timer.start();
|
||||
return false;
|
||||
}
|
||||
|
@ -211,7 +213,8 @@ void SettingsStorage::storeValue(const QString &key, const QVariant &value)
|
|||
const QWriteLocker locker(&m_lock);
|
||||
|
||||
QVariant ¤tValue = m_data[realKey];
|
||||
if (currentValue != value) {
|
||||
if (currentValue != value)
|
||||
{
|
||||
m_dirty = true;
|
||||
currentValue = value;
|
||||
m_timer.start();
|
||||
|
@ -222,7 +225,8 @@ void SettingsStorage::removeValue(const QString &key)
|
|||
{
|
||||
const QString realKey = mapKey(key);
|
||||
const QWriteLocker locker(&m_lock);
|
||||
if (m_data.remove(realKey) > 0) {
|
||||
if (m_data.remove(realKey) > 0)
|
||||
{
|
||||
m_dirty = true;
|
||||
m_timer.start();
|
||||
}
|
||||
|
@ -233,7 +237,8 @@ QVariantHash TransactionalSettings::read() const
|
|||
QVariantHash 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
|
||||
// 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
|
||||
|
@ -249,7 +254,8 @@ QVariantHash TransactionalSettings::read() const
|
|||
Utils::Fs::forceRemove(finalPath);
|
||||
QFile::rename(newPath, finalPath);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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
|
||||
// replace qBittorrent.ini/qBittorrent.conf with it.
|
||||
const QString newPath = serialize(m_name + QLatin1String("_new"), data);
|
||||
if (newPath.isEmpty()) {
|
||||
if (newPath.isEmpty())
|
||||
{
|
||||
Utils::Fs::forceRemove(newPath);
|
||||
return false;
|
||||
}
|
||||
|
@ -301,7 +308,8 @@ QString TransactionalSettings::serialize(const QString &name, const QVariantHash
|
|||
|
||||
settings->sync(); // Important to get error status
|
||||
|
||||
switch (settings->status()) {
|
||||
switch (settings->status())
|
||||
{
|
||||
case QSettings::NoError:
|
||||
return settings->fileName();
|
||||
case QSettings::AccessError:
|
||||
|
|
|
@ -73,7 +73,8 @@ TorrentFilter::TorrentFilter(const QString &filter, const QStringSet &hashSet, c
|
|||
|
||||
bool TorrentFilter::setType(Type type)
|
||||
{
|
||||
if (m_type != type) {
|
||||
if (m_type != type)
|
||||
{
|
||||
m_type = type;
|
||||
return true;
|
||||
}
|
||||
|
@ -113,7 +114,8 @@ bool TorrentFilter::setTypeByName(const QString &filter)
|
|||
|
||||
bool TorrentFilter::setHashSet(const QStringSet &hashSet)
|
||||
{
|
||||
if (m_hashSet != hashSet) {
|
||||
if (m_hashSet != hashSet)
|
||||
{
|
||||
m_hashSet = hashSet;
|
||||
return true;
|
||||
}
|
||||
|
@ -126,7 +128,8 @@ bool TorrentFilter::setCategory(const QString &category)
|
|||
// QString::operator==() doesn't distinguish between empty and null strings.
|
||||
if ((m_category != category)
|
||||
|| (m_category.isNull() && !category.isNull())
|
||||
|| (!m_category.isNull() && category.isNull())) {
|
||||
|| (!m_category.isNull() && category.isNull()))
|
||||
{
|
||||
m_category = category;
|
||||
return true;
|
||||
}
|
||||
|
@ -139,7 +142,8 @@ bool TorrentFilter::setTag(const QString &tag)
|
|||
// QString::operator==() doesn't distinguish between empty and null strings.
|
||||
if ((m_tag != tag)
|
||||
|| (m_tag.isNull() && !tag.isNull())
|
||||
|| (!m_tag.isNull() && tag.isNull())) {
|
||||
|| (!m_tag.isNull() && tag.isNull()))
|
||||
{
|
||||
m_tag = tag;
|
||||
return true;
|
||||
}
|
||||
|
@ -156,7 +160,8 @@ bool TorrentFilter::match(const TorrentHandle *const torrent) const
|
|||
|
||||
bool TorrentFilter::matchState(const BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
switch (m_type) {
|
||||
switch (m_type)
|
||||
{
|
||||
case All:
|
||||
return true;
|
||||
case Downloading:
|
||||
|
|
|
@ -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))));
|
||||
int head = 0;
|
||||
while (head < in.size()) {
|
||||
while (head < in.size())
|
||||
{
|
||||
int end = in.indexOf(sep, head);
|
||||
if (end < 0)
|
||||
end = in.size();
|
||||
|
|
|
@ -53,7 +53,8 @@ namespace
|
|||
{
|
||||
QProcess proc;
|
||||
proc.start(exeName, {"--version"}, QIODevice::ReadOnly);
|
||||
if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit)) {
|
||||
if (proc.waitForFinished() && (proc.exitCode() == QProcess::NormalExit))
|
||||
{
|
||||
QByteArray procOutput = proc.readAllStandardOutput();
|
||||
if (procOutput.isEmpty())
|
||||
procOutput = proc.readAllStandardError();
|
||||
|
@ -71,10 +72,12 @@ namespace
|
|||
const QString versionStr = outputSplit[1];
|
||||
const int idx = versionStr.indexOf(QRegularExpression("[^\\.\\d]"));
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
info = {exeName, versionStr.left(idx)};
|
||||
}
|
||||
catch (const std::runtime_error &) {
|
||||
catch (const std::runtime_error &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -102,12 +105,14 @@ namespace
|
|||
DWORD cMaxSubKeyLen = 0;
|
||||
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
|
||||
LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
|
||||
DWORD cName;
|
||||
|
||||
for (DWORD i = 0; i < cSubKeys; ++i) {
|
||||
for (DWORD i = 0; i < cSubKeys; ++i)
|
||||
{
|
||||
cName = cMaxSubKeyLen;
|
||||
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
|
||||
if (res == ERROR_SUCCESS)
|
||||
|
@ -127,7 +132,8 @@ namespace
|
|||
DWORD type = 0;
|
||||
DWORD cbData = 0;
|
||||
LPWSTR lpValueName = NULL;
|
||||
if (!name.isEmpty()) {
|
||||
if (!name.isEmpty())
|
||||
{
|
||||
lpValueName = new WCHAR[name.size() + 1];
|
||||
name.toWCharArray(lpValueName);
|
||||
lpValueName[name.size()] = 0;
|
||||
|
@ -141,7 +147,8 @@ namespace
|
|||
if (lpValueName)
|
||||
delete[] lpValueName;
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
lpData[cBuffer - 1] = 0;
|
||||
result = QString::fromWCharArray(lpData);
|
||||
}
|
||||
|
@ -169,13 +176,15 @@ namespace
|
|||
HKEY hkPythonCore;
|
||||
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
QStringList versions = getRegSubkeys(hkPythonCore);
|
||||
qDebug("Python versions nb: %d", versions.size());
|
||||
versions.sort();
|
||||
|
||||
bool found = false;
|
||||
while (!found && !versions.empty()) {
|
||||
while (!found && !versions.empty())
|
||||
{
|
||||
const QString version = versions.takeLast() + "\\InstallPath";
|
||||
LPWSTR lpSubkey = new WCHAR[version.size() + 1];
|
||||
version.toWCharArray(lpSubkey);
|
||||
|
@ -185,19 +194,23 @@ namespace
|
|||
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
|
||||
delete[] lpSubkey;
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
qDebug("Detected possible Python v%s location", qUtf8Printable(version));
|
||||
path = getRegValue(hkInstallPath);
|
||||
::RegCloseKey(hkInstallPath);
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
if (!path.isEmpty())
|
||||
{
|
||||
const QDir baseDir {path};
|
||||
|
||||
if (baseDir.exists("python3.exe")) {
|
||||
if (baseDir.exists("python3.exe"))
|
||||
{
|
||||
found = true;
|
||||
path = baseDir.filePath("python3.exe");
|
||||
}
|
||||
else if (baseDir.exists("python.exe")) {
|
||||
else if (baseDir.exists("python.exe"))
|
||||
{
|
||||
found = true;
|
||||
path = baseDir.filePath("python.exe");
|
||||
}
|
||||
|
@ -230,7 +243,8 @@ namespace
|
|||
|
||||
// Fallback: Detect python from default locations
|
||||
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"};
|
||||
if (QFile::exists(py3Path))
|
||||
return py3Path;
|
||||
|
@ -258,7 +272,8 @@ bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
|
|||
PythonInfo Utils::ForeignApps::pythonInfo()
|
||||
{
|
||||
static PythonInfo pyInfo;
|
||||
if (!pyInfo.isValid()) {
|
||||
if (!pyInfo.isValid())
|
||||
{
|
||||
if (testPythonInstallation("python3", pyInfo))
|
||||
return pyInfo;
|
||||
|
||||
|
|
|
@ -113,7 +113,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
|||
if (path.isEmpty() || !QDir(path).exists())
|
||||
return true;
|
||||
|
||||
const QStringList deleteFilesList = {
|
||||
const QStringList deleteFilesList =
|
||||
{
|
||||
// Windows
|
||||
QLatin1String("Thumbs.db"),
|
||||
QLatin1String("desktop.ini"),
|
||||
|
@ -132,7 +133,8 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
|||
std::sort(dirList.begin(), dirList.end()
|
||||
, [](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);
|
||||
// A deeper folder may have not been removed in the previous iteration
|
||||
// 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
|
||||
qint64 size = 0;
|
||||
QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
||||
while (iter.hasNext()) {
|
||||
while (iter.hasNext())
|
||||
{
|
||||
iter.next();
|
||||
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;
|
||||
|
||||
const int readSize = 1024 * 1024; // 1 MiB
|
||||
while (!f1.atEnd() && !f2.atEnd()) {
|
||||
while (!f1.atEnd() && !f2.atEnd())
|
||||
{
|
||||
if (f1.read(readSize) != f2.read(readSize))
|
||||
return false;
|
||||
}
|
||||
|
@ -243,15 +247,18 @@ bool Utils::Fs::isValidFileSystemName(const QString &name, const bool allowSepar
|
|||
if (name.isEmpty()) return false;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const QRegularExpression regex {allowSeparators
|
||||
const QRegularExpression regex
|
||||
{allowSeparators
|
||||
? QLatin1String("[:?\"*<>|]")
|
||||
: QLatin1String("[\\\\/:?\"*<>|]")};
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QRegularExpression regex {allowSeparators
|
||||
const QRegularExpression regex
|
||||
{allowSeparators
|
||||
? QLatin1String("[\\0:]")
|
||||
: QLatin1String("[\\0/:]")};
|
||||
#else
|
||||
const QRegularExpression regex {allowSeparators
|
||||
const QRegularExpression regex
|
||||
{allowSeparators
|
||||
? QLatin1String("[\\0]")
|
||||
: QLatin1String("[\\0/]")};
|
||||
#endif
|
||||
|
@ -271,7 +278,8 @@ QString Utils::Fs::branchPath(const QString &filePath, QString *removed)
|
|||
if (ret.endsWith('/'))
|
||||
ret.chop(1);
|
||||
const int slashIndex = ret.lastIndexOf('/');
|
||||
if (slashIndex >= 0) {
|
||||
if (slashIndex >= 0)
|
||||
{
|
||||
if (removed)
|
||||
*removed = ret.mid(slashIndex + 1);
|
||||
ret = ret.left(slashIndex);
|
||||
|
@ -312,7 +320,8 @@ QString Utils::Fs::tempPath()
|
|||
bool Utils::Fs::isRegularFile(const QString &path)
|
||||
{
|
||||
struct ::stat st;
|
||||
if (::stat(path.toUtf8().constData(), &st) != 0) {
|
||||
if (::stat(path.toUtf8().constData(), &st) != 0)
|
||||
{
|
||||
// analyse erno and log the error
|
||||
const auto err = errno;
|
||||
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:
|
||||
// 1. /usr/include/linux/magic.h
|
||||
// 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 0x6969: // NFS_SUPER_MAGIC
|
||||
case 0x517B: // SMB_SUPER_MAGIC
|
||||
|
|
|
@ -68,10 +68,12 @@ QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *
|
|||
output.reserve(deflateBound(&strm, data.size()));
|
||||
|
||||
// feed to deflate
|
||||
while (strm.avail_in > 0) {
|
||||
while (strm.avail_in > 0)
|
||||
{
|
||||
result = deflate(&strm, Z_NO_FLUSH);
|
||||
|
||||
if (result != Z_OK) {
|
||||
if (result != Z_OK)
|
||||
{
|
||||
deflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
@ -82,7 +84,8 @@ QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *
|
|||
}
|
||||
|
||||
// flush the rest from deflate
|
||||
while (result != Z_STREAM_END) {
|
||||
while (result != Z_STREAM_END)
|
||||
{
|
||||
result = deflate(&strm, Z_FINISH);
|
||||
|
||||
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);
|
||||
|
||||
// run inflate
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
result = inflate(&strm, Z_NO_FLUSH);
|
||||
|
||||
if (result == Z_STREAM_END) {
|
||||
if (result == Z_STREAM_END)
|
||||
{
|
||||
output.append(tmpBuf.data(), (BUFSIZE - strm.avail_out));
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != Z_OK) {
|
||||
if (result != Z_OK)
|
||||
{
|
||||
inflateEnd(&strm);
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ Utils::IO::FileDeviceOutputIterator::FileDeviceOutputIterator(QFileDevice &devic
|
|||
|
||||
Utils::IO::FileDeviceOutputIterator::~FileDeviceOutputIterator()
|
||||
{
|
||||
if (m_buffer.use_count() == 1) {
|
||||
if (m_buffer.use_count() == 1)
|
||||
{
|
||||
if (m_device->error() == QFileDevice::NoError)
|
||||
m_device->write(*m_buffer);
|
||||
m_buffer->clear();
|
||||
|
@ -51,7 +52,8 @@ Utils::IO::FileDeviceOutputIterator::~FileDeviceOutputIterator()
|
|||
Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operator=(const char c)
|
||||
{
|
||||
m_buffer->append(c);
|
||||
if (m_buffer->size() >= m_bufferSize) {
|
||||
if (m_buffer->size() >= m_bufferSize)
|
||||
{
|
||||
if (m_device->error() == QFileDevice::NoError)
|
||||
m_device->write(*m_buffer);
|
||||
m_buffer->clear();
|
||||
|
|
|
@ -63,7 +63,8 @@
|
|||
|
||||
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", "KiB", "kibibytes (1024 bytes)"),
|
||||
QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
|
||||
|
@ -85,7 +86,8 @@ namespace
|
|||
int i = 0;
|
||||
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.;
|
||||
++i;
|
||||
}
|
||||
|
@ -119,13 +121,16 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
|||
if (GetLastError() != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
if (action == ShutdownDialogAction::Suspend) {
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
{
|
||||
::SetSuspendState(false, false, false);
|
||||
}
|
||||
else if (action == ShutdownDialogAction::Hibernate) {
|
||||
else if (action == ShutdownDialogAction::Hibernate)
|
||||
{
|
||||
::SetSuspendState(true, false, false);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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);
|
||||
msg.toWCharArray(msgWchar.get());
|
||||
|
@ -171,11 +176,13 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
|||
|
||||
#elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB))
|
||||
// Use dbus to power off / suspend the system
|
||||
if (action != ShutdownDialogAction::Shutdown) {
|
||||
if (action != ShutdownDialogAction::Shutdown)
|
||||
{
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager", QDBusConnection::systemBus());
|
||||
if (login1Iface.isValid()) {
|
||||
if (login1Iface.isValid())
|
||||
{
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
login1Iface.call("Suspend", false);
|
||||
else
|
||||
|
@ -185,7 +192,8 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
|||
// Else, other recent systems use UPower
|
||||
QDBusInterface upowerIface("org.freedesktop.UPower", "/org/freedesktop/UPower",
|
||||
"org.freedesktop.UPower", QDBusConnection::systemBus());
|
||||
if (upowerIface.isValid()) {
|
||||
if (upowerIface.isValid())
|
||||
{
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
upowerIface.call("Suspend");
|
||||
else
|
||||
|
@ -201,18 +209,21 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
|||
else
|
||||
halIface.call("Hibernate");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface("org.freedesktop.login1", "/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager", QDBusConnection::systemBus());
|
||||
if (login1Iface.isValid()) {
|
||||
if (login1Iface.isValid())
|
||||
{
|
||||
login1Iface.call("PowerOff", false);
|
||||
return;
|
||||
}
|
||||
// Else, other recent systems use ConsoleKit
|
||||
QDBusInterface consolekitIface("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager",
|
||||
"org.freedesktop.ConsoleKit.Manager", QDBusConnection::systemBus());
|
||||
if (consolekitIface.isValid()) {
|
||||
if (consolekitIface.isValid())
|
||||
{
|
||||
consolekitIface.call("Stop");
|
||||
return;
|
||||
}
|
||||
|
@ -251,7 +262,8 @@ QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed)
|
|||
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
|
||||
{
|
||||
// friendlyUnit's number of digits after the decimal point
|
||||
switch (unit) {
|
||||
switch (unit)
|
||||
{
|
||||
case SizeUnit::Byte:
|
||||
return 0;
|
||||
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)
|
||||
{
|
||||
static const QSet<QString> multimediaExtensions = {
|
||||
static const QSet<QString> multimediaExtensions =
|
||||
{
|
||||
"3GP",
|
||||
"AAC",
|
||||
"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));
|
||||
|
||||
qlonglong hours = (minutes / 60);
|
||||
if (hours < 24) {
|
||||
if (hours < 24)
|
||||
{
|
||||
minutes -= (hours * 60);
|
||||
return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours), QString::number(minutes));
|
||||
}
|
||||
|
||||
qlonglong days = (hours / 24);
|
||||
if (days < 365) {
|
||||
if (days < 365)
|
||||
{
|
||||
hours -= (days * 24);
|
||||
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
|
||||
QString Utils::Misc::windowsSystemPath()
|
||||
{
|
||||
static const QString path = []() -> QString {
|
||||
static const QString path = []() -> QString
|
||||
{
|
||||
WCHAR systemPath[MAX_PATH] = {0};
|
||||
GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
|
||||
return QString::fromWCharArray(systemPath);
|
||||
|
|
|
@ -70,12 +70,14 @@ namespace Utils
|
|||
QHostAddress protocolEquivalentAddress;
|
||||
bool addrConversionOk = false;
|
||||
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
// always succeeds
|
||||
protocolEquivalentAddress = QHostAddress(addr.toIPv6Address());
|
||||
addrConversionOk = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// only succeeds when addr is an ipv4-mapped ipv6 address
|
||||
protocolEquivalentAddress = QHostAddress(addr.toIPv4Address(&addrConversionOk));
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ QByteArray Utils::Password::PBKDF2::generate(const QString &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()}};
|
||||
|
||||
std::array<unsigned char, 64> outBuf {};
|
||||
|
|
|
@ -82,7 +82,8 @@ namespace
|
|||
|
||||
int posL = 0;
|
||||
int posR = 0;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
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
|
||||
|
||||
|
@ -91,12 +92,14 @@ namespace
|
|||
// Compare only non-digits.
|
||||
// Numbers should be compared as a whole
|
||||
// otherwise the string->int conversion can yield a wrong value
|
||||
if ((leftChar == rightChar) && !leftChar.isDigit()) {
|
||||
if ((leftChar == rightChar) && !leftChar.isDigit())
|
||||
{
|
||||
// compare next character
|
||||
++posL;
|
||||
++posR;
|
||||
}
|
||||
else if (leftChar.isDigit() && rightChar.isDigit()) {
|
||||
else if (leftChar.isDigit() && rightChar.isDigit())
|
||||
{
|
||||
// Both are digits, compare the numbers
|
||||
|
||||
const auto numberView = [](const QString &str, int &pos) -> QStringRef
|
||||
|
@ -114,7 +117,8 @@ namespace
|
|||
return (numViewL.length() - numViewR.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 numR = numViewR[i];
|
||||
|
||||
|
@ -125,7 +129,8 @@ namespace
|
|||
// String + digits do match and we haven't hit the end of both strings
|
||||
// then continue to consume the remainings
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
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
|
||||
// https://doc.qt.io/qt-5/threads-reentrancy.html
|
||||
if (caseSensitivity == Qt::CaseSensitive) {
|
||||
if (caseSensitivity == Qt::CaseSensitive)
|
||||
{
|
||||
#ifdef QBT_USES_QTHREADSTORAGE
|
||||
static QThreadStorage<NaturalCompare> nCmp;
|
||||
if (!nCmp.hasLocalData())
|
||||
|
|
|
@ -58,7 +58,8 @@ namespace Utils
|
|||
{
|
||||
if (str.length() < 2) return str;
|
||||
|
||||
for (const QChar quote : quotes) {
|
||||
for (const QChar quote : quotes)
|
||||
{
|
||||
if (str.startsWith(quote) && str.endsWith(quote))
|
||||
return str.mid(1, (str.length() - 2));
|
||||
}
|
||||
|
|
|
@ -151,10 +151,12 @@ namespace Utils
|
|||
template <typename StringClassWithSplitMethod>
|
||||
static Version tryParse(const StringClassWithSplitMethod &s, const Version &defaultVersion)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
return Version(s);
|
||||
}
|
||||
catch (const std::runtime_error &er) {
|
||||
catch (const std::runtime_error &er)
|
||||
{
|
||||
qDebug() << "Error parsing version:" << er.what();
|
||||
return defaultVersion;
|
||||
}
|
||||
|
@ -172,7 +174,8 @@ namespace Utils
|
|||
|
||||
bool ok = false;
|
||||
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));
|
||||
if (!ok)
|
||||
throw std::runtime_error("Can not parse version component");
|
||||
|
|
|
@ -70,21 +70,24 @@ AboutDialog::AboutDialog(QWidget *parent)
|
|||
|
||||
// Thanks
|
||||
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()));
|
||||
thanksfile.close();
|
||||
}
|
||||
|
||||
// Translation
|
||||
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()));
|
||||
translatorsfile.close();
|
||||
}
|
||||
|
||||
// License
|
||||
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()));
|
||||
licensefile.close();
|
||||
}
|
||||
|
|
|
@ -230,7 +230,8 @@ void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorre
|
|||
{
|
||||
auto *dlg = new AddNewTorrentDialog(inParams, parent);
|
||||
|
||||
if (Net::DownloadManager::hasSupportedScheme(source)) {
|
||||
if (Net::DownloadManager::hasSupportedScheme(source))
|
||||
{
|
||||
// Launch downloader
|
||||
Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
|
||||
|
@ -262,7 +263,8 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
|||
|
||||
QString error;
|
||||
m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(decodedPath, &error);
|
||||
if (!m_torrentInfo.isValid()) {
|
||||
if (!m_torrentInfo.isValid())
|
||||
{
|
||||
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.")
|
||||
.arg(Utils::Fs::toNativePath(decodedPath), error));
|
||||
|
@ -280,19 +282,24 @@ bool AddNewTorrentDialog::loadTorrentImpl()
|
|||
const BitTorrent::InfoHash infoHash = m_torrentInfo.hash();
|
||||
|
||||
// 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);
|
||||
if (torrent) {
|
||||
if (torrent->isPrivate() || m_torrentInfo.isPrivate()) {
|
||||
if (torrent)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
torrent->addTrackers(m_torrentInfo.trackers());
|
||||
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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok);
|
||||
}
|
||||
return false;
|
||||
|
@ -307,7 +314,8 @@ bool AddNewTorrentDialog::loadTorrentImpl()
|
|||
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
|
@ -316,19 +324,24 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
|||
|
||||
const BitTorrent::InfoHash infoHash = magnetUri.hash();
|
||||
// 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);
|
||||
if (torrent) {
|
||||
if (torrent->isPrivate()) {
|
||||
if (torrent)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
torrent->addTrackers(magnetUri.trackers());
|
||||
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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link is already queued for processing."), QMessageBox::Ok);
|
||||
}
|
||||
return false;
|
||||
|
@ -395,15 +408,18 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
|
|||
// Determine torrent size
|
||||
qlonglong torrentSize = 0;
|
||||
|
||||
if (m_hasMetadata) {
|
||||
if (m_contentModel) {
|
||||
if (m_hasMetadata)
|
||||
{
|
||||
if (m_contentModel)
|
||||
{
|
||||
const QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
|
||||
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
|
||||
for (int i = 0; i < priorities.size(); ++i)
|
||||
if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
|
||||
torrentSize += m_torrentInfo.fileSize(i);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
torrentSize = m_torrentInfo.totalSize();
|
||||
}
|
||||
}
|
||||
|
@ -426,7 +442,8 @@ void AddNewTorrentDialog::categoryChanged(int 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());
|
||||
m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath));
|
||||
updateDiskSpaceLabel();
|
||||
|
@ -436,7 +453,8 @@ void AddNewTorrentDialog::categoryChanged(int index)
|
|||
void AddNewTorrentDialog::setSavePath(const QString &newPath)
|
||||
{
|
||||
int existingIndex = indexOfSavePath(newPath);
|
||||
if (existingIndex < 0) {
|
||||
if (existingIndex < 0)
|
||||
{
|
||||
// New path, prepend to combo box
|
||||
m_ui->savePath->insertItem(0, newPath);
|
||||
existingIndex = 0;
|
||||
|
@ -461,10 +479,12 @@ void AddNewTorrentDialog::saveTorrentFile()
|
|||
if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive))
|
||||
path += torrentFileExtension;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
m_torrentInfo.saveToFile(path);
|
||||
}
|
||||
catch (const RuntimeError &err) {
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
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)
|
||||
{
|
||||
for (const QModelIndex &index : selectedRows) {
|
||||
for (const QModelIndex &index : selectedRows)
|
||||
{
|
||||
m_contentModel->setData(
|
||||
m_contentModel->index(index.row(), PRIORITY, index.parent())
|
||||
, static_cast<int>(prio));
|
||||
|
@ -504,7 +525,8 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
|||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (selectedRows.size() == 1) {
|
||||
if (selectedRows.size() == 1)
|
||||
{
|
||||
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
|
||||
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();
|
||||
|
||||
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.savePath = savePath;
|
||||
saveSavePathHistory();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_torrentParams.useAutoTMM = TriStateBool::True;
|
||||
}
|
||||
|
||||
|
@ -586,7 +610,8 @@ void AddNewTorrentDialog::accept()
|
|||
|
||||
void AddNewTorrentDialog::reject()
|
||||
{
|
||||
if (!m_hasMetadata) {
|
||||
if (!m_hasMetadata)
|
||||
{
|
||||
setMetadataProgressIndicator(false);
|
||||
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);
|
||||
|
||||
if (!metadata.isValid()) {
|
||||
if (!metadata.isValid())
|
||||
{
|
||||
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
|
||||
setMetadataProgressIndicator(false, tr("Invalid metadata"));
|
||||
return;
|
||||
|
@ -628,11 +654,13 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
|
|||
|
||||
void AddNewTorrentDialog::setupTreeview()
|
||||
{
|
||||
if (!m_hasMetadata) {
|
||||
if (!m_hasMetadata)
|
||||
{
|
||||
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
||||
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Set dialog title
|
||||
setWindowTitle(m_torrentInfo.name());
|
||||
|
||||
|
@ -662,7 +690,8 @@ void AddNewTorrentDialog::setupTreeview()
|
|||
|
||||
// Expand single-item folders recursively
|
||||
QModelIndex currentIndex;
|
||||
while (m_contentModel->rowCount(currentIndex) == 1) {
|
||||
while (m_contentModel->rowCount(currentIndex) == 1)
|
||||
{
|
||||
currentIndex = m_contentModel->index(0, 0, currentIndex);
|
||||
m_ui->contentTreeView->setExpanded(currentIndex, true);
|
||||
}
|
||||
|
@ -674,10 +703,12 @@ void AddNewTorrentDialog::setupTreeview()
|
|||
void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
QString error;
|
||||
switch (result.status) {
|
||||
switch (result.status)
|
||||
{
|
||||
case Net::DownloadStatus::Success:
|
||||
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")
|
||||
.arg(result.url, error));
|
||||
return;
|
||||
|
@ -705,13 +736,15 @@ void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &resu
|
|||
|
||||
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();
|
||||
m_ui->groupBoxSavePath->setEnabled(true);
|
||||
m_ui->savePath->blockSignals(false);
|
||||
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->savePath->blockSignals(true);
|
||||
m_ui->savePath->clear();
|
||||
|
|
|
@ -168,7 +168,8 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||
|
||||
#if defined(Q_OS_WIN)
|
||||
BitTorrent::OSMemoryPriority prio = BitTorrent::OSMemoryPriority::Normal;
|
||||
switch (m_comboBoxOSMemoryPriority.currentIndex()) {
|
||||
switch (m_comboBoxOSMemoryPriority.currentIndex())
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
prio = BitTorrent::OSMemoryPriority::Normal;
|
||||
|
@ -248,12 +249,14 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||
pref->resolvePeerCountries(m_checkBoxResolveCountries.isChecked());
|
||||
pref->resolvePeerHostNames(m_checkBoxResolveHosts.isChecked());
|
||||
// Network interface
|
||||
if (m_comboBoxInterface.currentIndex() == 0) {
|
||||
if (m_comboBoxInterface.currentIndex() == 0)
|
||||
{
|
||||
// All interfaces (default)
|
||||
session->setNetworkInterface(QString());
|
||||
session->setNetworkInterfaceName(QString());
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
session->setNetworkInterface(m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString());
|
||||
session->setNetworkInterfaceName(m_comboBoxInterface.currentText());
|
||||
}
|
||||
|
@ -336,21 +339,25 @@ void AdvancedSettings::updateInterfaceAddressCombo()
|
|||
|
||||
const auto populateCombo = [this](const QHostAddress &addr)
|
||||
{
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
const QString str = addr.toString();
|
||||
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();
|
||||
m_comboBoxInterfaceAddress.addItem(str, str);
|
||||
}
|
||||
};
|
||||
|
||||
if (ifaceName.isEmpty()) {
|
||||
if (ifaceName.isEmpty())
|
||||
{
|
||||
for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
|
||||
populateCombo(addr);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
|
||||
const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : addresses)
|
||||
|
@ -386,7 +393,8 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
#if defined(Q_OS_WIN)
|
||||
m_comboBoxOSMemoryPriority.addItems({tr("Normal"), tr("Below normal"), tr("Medium"), tr("Low"), tr("Very low")});
|
||||
int OSMemoryPriorityIndex = 0;
|
||||
switch (session->getOSMemoryPriority()) {
|
||||
switch (session->getOSMemoryPriority())
|
||||
{
|
||||
default:
|
||||
case BitTorrent::OSMemoryPriority::Normal:
|
||||
OSMemoryPriorityIndex = 0;
|
||||
|
@ -580,16 +588,19 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||
const QString currentInterface = session->networkInterface();
|
||||
bool interfaceExists = currentInterface.isEmpty();
|
||||
int i = 1;
|
||||
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces())) {
|
||||
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces()))
|
||||
{
|
||||
m_comboBoxInterface.addItem(iface.humanReadableName(), iface.name());
|
||||
if (!currentInterface.isEmpty() && (iface.name() == currentInterface)) {
|
||||
if (!currentInterface.isEmpty() && (iface.name() == currentInterface))
|
||||
{
|
||||
m_comboBoxInterface.setCurrentIndex(i);
|
||||
interfaceExists = true;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Saved interface does not exist, show it anyway
|
||||
if (!interfaceExists) {
|
||||
if (!interfaceExists)
|
||||
{
|
||||
m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface);
|
||||
m_comboBoxInterface.setCurrentIndex(i);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,8 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con
|
|||
d.m_ui->textEdit->setInputMethodHints(inputMethodHints);
|
||||
|
||||
d.m_ui->textEdit->selectAll();
|
||||
if (excludeExtension) {
|
||||
if (excludeExtension)
|
||||
{
|
||||
int lastDotIndex = text.lastIndexOf('.');
|
||||
if ((lastDotIndex > 3) && (text.mid(lastDotIndex - 4, 4).toLower() == ".tar"))
|
||||
lastDotIndex -= 4;
|
||||
|
@ -86,7 +87,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
|
|||
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
|
||||
#endif
|
||||
|
||||
if (!windowTitle().isEmpty()) {
|
||||
if (!windowTitle().isEmpty())
|
||||
{
|
||||
// not really the font metrics in window title, so we enlarge it a bit,
|
||||
// including the small icon and close button width
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
|
@ -97,7 +99,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
|
|||
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))
|
||||
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
|
||||
#else
|
||||
|
@ -109,7 +112,8 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
|
|||
// Now resize the dialog to fit the contents
|
||||
// max width of text from either of: label, title, textedit
|
||||
// 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()};
|
||||
Utils::Gui::resize(this, size);
|
||||
}
|
||||
|
|
|
@ -64,18 +64,21 @@ BanListOptionsDialog::~BanListOptionsDialog()
|
|||
|
||||
void BanListOptionsDialog::on_buttonBox_accepted()
|
||||
{
|
||||
if (m_modified) {
|
||||
if (m_modified)
|
||||
{
|
||||
// save to session
|
||||
QStringList IPList;
|
||||
// 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);
|
||||
IPList << index.data().toString();
|
||||
}
|
||||
BitTorrent::Session::instance()->setBannedIPs(IPList);
|
||||
QDialog::accept();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QDialog::reject();
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +86,8 @@ void BanListOptionsDialog::on_buttonBox_accepted()
|
|||
void BanListOptionsDialog::on_buttonBanIP_clicked()
|
||||
{
|
||||
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."));
|
||||
return;
|
||||
}
|
||||
|
@ -91,9 +95,11 @@ void BanListOptionsDialog::on_buttonBanIP_clicked()
|
|||
// QHostAddress::toString() result format follows RFC5952;
|
||||
// thus we avoid duplicate entries pointing to the same address
|
||||
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);
|
||||
if (ip == index.data().toString()) {
|
||||
if (ip == index.data().toString())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Warning"), tr("The entered IP is already banned."));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ public:
|
|||
~CategoryModelItem()
|
||||
{
|
||||
clear();
|
||||
if (m_parent) {
|
||||
if (m_parent)
|
||||
{
|
||||
m_parent->m_torrentsCount -= m_torrentsCount;
|
||||
const QString uid = m_parent->m_children.key(this);
|
||||
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());
|
||||
|
||||
if ((index.column() == 0) && (role == Qt::DecorationRole)) {
|
||||
if ((index.column() == 0) && (role == Qt::DecorationRole))
|
||||
{
|
||||
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)"))
|
||||
.arg(item->name()).arg(item->torrentsCount());
|
||||
}
|
||||
|
||||
if ((index.column() == 0) && (role == Qt::UserRole)) {
|
||||
if ((index.column() == 0) && (role == Qt::UserRole))
|
||||
{
|
||||
return item->torrentsCount();
|
||||
}
|
||||
|
||||
|
@ -306,7 +310,8 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName)
|
|||
{
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
|
||||
if (m_isSubcategoriesEnabled) {
|
||||
if (m_isSubcategoriesEnabled)
|
||||
{
|
||||
QStringList expanded = BitTorrent::Session::expandCategory(categoryName);
|
||||
if (expanded.count() > 1)
|
||||
parent = findItem(expanded[expanded.count() - 2]);
|
||||
|
@ -322,7 +327,8 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName)
|
|||
void CategoryFilterModel::categoryRemoved(const QString &categoryName)
|
||||
{
|
||||
auto item = findItem(categoryName);
|
||||
if (item) {
|
||||
if (item)
|
||||
{
|
||||
QModelIndex i = index(item);
|
||||
beginRemoveRows(i.parent(), i.row(), i.row());
|
||||
delete item;
|
||||
|
@ -357,7 +363,8 @@ void CategoryFilterModel::torrentCategoryChanged(BitTorrent::TorrentHandle *cons
|
|||
|
||||
item->decreaseTorrentsCount();
|
||||
i = index(item);
|
||||
while (i.isValid()) {
|
||||
while (i.isValid())
|
||||
{
|
||||
emit dataChanged(i, i);
|
||||
i = parent(i);
|
||||
}
|
||||
|
@ -367,7 +374,8 @@ void CategoryFilterModel::torrentCategoryChanged(BitTorrent::TorrentHandle *cons
|
|||
|
||||
item->increaseTorrentsCount();
|
||||
i = index(item);
|
||||
while (i.isValid()) {
|
||||
while (i.isValid())
|
||||
{
|
||||
emit dataChanged(i, i);
|
||||
i = parent(i);
|
||||
}
|
||||
|
@ -404,13 +412,17 @@ void CategoryFilterModel::populate()
|
|||
, [](Torrent *torrent) { return torrent->category().isEmpty(); })));
|
||||
|
||||
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();
|
||||
if (m_isSubcategoriesEnabled) {
|
||||
if (m_isSubcategoriesEnabled)
|
||||
{
|
||||
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);
|
||||
if (!parent->hasChild(subcatName)) {
|
||||
if (!parent->hasChild(subcatName))
|
||||
{
|
||||
new CategoryModelItem(
|
||||
parent, subcatName
|
||||
, std::count_if(torrents.cbegin(), torrents.cend()
|
||||
|
@ -419,7 +431,8 @@ void CategoryFilterModel::populate()
|
|||
parent = parent->child(subcatName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
new CategoryModelItem(
|
||||
m_rootItem, category
|
||||
, std::count_if(torrents.begin(), torrents.end()
|
||||
|
@ -437,7 +450,8 @@ CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
|
|||
return m_rootItem->child(fullName);
|
||||
|
||||
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);
|
||||
if (!item->hasChild(subcatName)) return nullptr;
|
||||
item = item->child(subcatName);
|
||||
|
|
|
@ -44,7 +44,8 @@ namespace
|
|||
QString getCategoryFilter(const CategoryFilterProxyModel *const model, const QModelIndex &index)
|
||||
{
|
||||
QString categoryFilter; // Defaults to All
|
||||
if (index.isValid()) {
|
||||
if (index.isValid())
|
||||
{
|
||||
if (!index.parent().isValid() && (index.row() == 1))
|
||||
categoryFilter = ""; // Uncategorized
|
||||
else if (index.parent().isValid() || (index.row() > 1))
|
||||
|
@ -115,8 +116,10 @@ void CategoryFilterWidget::showMenu(const QPoint &)
|
|||
connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory);
|
||||
|
||||
const auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) {
|
||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) {
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
||||
{
|
||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||
{
|
||||
const QAction *addSubAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-add")
|
||||
, tr("Add subcategory..."));
|
||||
|
@ -175,7 +178,8 @@ QSize CategoryFilterWidget::sizeHint() const
|
|||
// otherwise widget will not correctly adjust the
|
||||
// size when subcategories are used.
|
||||
const QSize viewportSize {viewportSizeHint()};
|
||||
return {
|
||||
return
|
||||
{
|
||||
viewportSize.width(),
|
||||
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.
|
||||
QModelIndex p = parent;
|
||||
while (p.isValid()) {
|
||||
while (p.isValid())
|
||||
{
|
||||
if (!isExpanded(p))
|
||||
expand(p);
|
||||
p = model()->parent(p);
|
||||
|
@ -221,7 +226,8 @@ void CategoryFilterWidget::editCategory()
|
|||
void CategoryFilterWidget::removeCategory()
|
||||
{
|
||||
const auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) {
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
||||
{
|
||||
BitTorrent::Session::instance()->removeCategory(
|
||||
static_cast<CategoryFilterProxyModel *>(model())->categoryName(selectedRows.first()));
|
||||
updateGeometry();
|
||||
|
@ -231,7 +237,8 @@ void CategoryFilterWidget::removeCategory()
|
|||
void CategoryFilterWidget::removeUnusedCategories()
|
||||
{
|
||||
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)
|
||||
session->removeCategory(category);
|
||||
}
|
||||
|
|
|
@ -43,8 +43,10 @@ QList<QNetworkCookie> CookiesModel::cookies() const
|
|||
|
||||
QVariant CookiesModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal)) {
|
||||
switch (section) {
|
||||
if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal))
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
case COL_DOMAIN:
|
||||
return tr("Domain");
|
||||
case COL_PATH:
|
||||
|
@ -96,7 +98,8 @@ QVariant CookiesModel::data(const QModelIndex &index, int role) const
|
|||
|| ((role != Qt::DisplayRole) && (role != Qt::EditRole)))
|
||||
return {};
|
||||
|
||||
switch (index.column()) {
|
||||
switch (index.column())
|
||||
{
|
||||
case COL_DOMAIN:
|
||||
return m_cookies[index.row()].domain();
|
||||
case COL_PATH:
|
||||
|
@ -116,7 +119,8 @@ bool CookiesModel::setData(const QModelIndex &index, const QVariant &value, int
|
|||
{
|
||||
if (role != Qt::EditRole) return false;
|
||||
|
||||
switch (index.column()) {
|
||||
switch (index.column())
|
||||
{
|
||||
case COL_DOMAIN:
|
||||
m_cookies[index.row()].setDomain(value.toString());
|
||||
break;
|
||||
|
|
|
@ -71,7 +71,8 @@ DownloadFromURLDialog::DownloadFromURLDialog(QWidget *parent)
|
|||
const QVector<QStringRef> clipboardList = clipboardText.splitRef('\n');
|
||||
|
||||
QSet<QString> uniqueURLs;
|
||||
for (QStringRef strRef : clipboardList) {
|
||||
for (QStringRef strRef : clipboardList)
|
||||
{
|
||||
strRef = strRef.trimmed();
|
||||
if (strRef.isEmpty()) continue;
|
||||
|
||||
|
@ -96,14 +97,16 @@ void DownloadFromURLDialog::downloadButtonClicked()
|
|||
const QVector<QStringRef> urls = plainText.splitRef('\n');
|
||||
|
||||
QSet<QString> uniqueURLs;
|
||||
for (QStringRef url : urls) {
|
||||
for (QStringRef url : urls)
|
||||
{
|
||||
url = url.trimmed();
|
||||
if (url.isEmpty()) continue;
|
||||
|
||||
uniqueURLs << url.toString();
|
||||
}
|
||||
|
||||
if (uniqueURLs.isEmpty()) {
|
||||
if (uniqueURLs.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("No URL entered"), tr("Please type at least one URL."));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -89,7 +89,8 @@ void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView
|
|||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// 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"));
|
||||
connect(copyAct, &QAction::triggered, view, &LogListView::copySelection);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,8 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::browseActionTriggered()
|
|||
QString directory = q->currentDirectory().isEmpty() ? QDir::homePath() : q->currentDirectory();
|
||||
|
||||
QString selectedPath;
|
||||
switch (m_mode) {
|
||||
switch (m_mode)
|
||||
{
|
||||
case FileSystemPathEdit::Mode::FileOpen:
|
||||
selectedPath = QFileDialog::getOpenFileName(q, dialogCaptionOrDefault(), directory, filter);
|
||||
break;
|
||||
|
@ -139,7 +140,8 @@ QString FileSystemPathEdit::FileSystemPathEditPrivate::dialogCaptionOrDefault()
|
|||
if (!m_dialogCaption.isEmpty())
|
||||
return m_dialogCaption;
|
||||
|
||||
switch (m_mode) {
|
||||
switch (m_mode)
|
||||
{
|
||||
case FileSystemPathEdit::Mode::FileOpen:
|
||||
case FileSystemPathEdit::Mode::FileSave:
|
||||
return defaultDialogCaptionForFile.tr();
|
||||
|
@ -155,7 +157,8 @@ void FileSystemPathEdit::FileSystemPathEditPrivate::modeChanged()
|
|||
{
|
||||
QStyle::StandardPixmap pixmap = QStyle::SP_DialogOpenButton;
|
||||
bool showDirsOnly = false;
|
||||
switch (m_mode) {
|
||||
switch (m_mode)
|
||||
{
|
||||
case FileSystemPathEdit::Mode::FileOpen:
|
||||
case FileSystemPathEdit::Mode::FileSave:
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -229,17 +232,21 @@ void FileSystemPathEdit::setFileNameFilter(const QString &val)
|
|||
// extract file masks
|
||||
const int openBracePos = val.indexOf(QLatin1Char('('), 0);
|
||||
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);
|
||||
if (filterString == QLatin1String("*")) { // no filters
|
||||
if (filterString == QLatin1String("*"))
|
||||
{ // no filters
|
||||
d->m_editor->setFilenameFilters({});
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QStringList filters = filterString.split(QLatin1Char(' '), QString::SkipEmptyParts);
|
||||
d->m_editor->setFilenameFilters(filters);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
d->m_editor->setFilenameFilters({});
|
||||
}
|
||||
#endif
|
||||
|
@ -261,7 +268,8 @@ void FileSystemPathEdit::onPathEdited()
|
|||
{
|
||||
Q_D(FileSystemPathEdit);
|
||||
QString newPath = selectedPath();
|
||||
if (newPath != d->m_lastSignaledPath) {
|
||||
if (newPath != d->m_lastSignaledPath)
|
||||
{
|
||||
emit selectedPathChanged(newPath);
|
||||
d->m_lastSignaledPath = newPath;
|
||||
d->m_editor->widget()->setToolTip(editWidgetText());
|
||||
|
|
|
@ -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
|
||||
// character in the components array, yet everything past the one before the last delimiter
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
@ -140,12 +141,14 @@ QValidator::State Private::FileSystemPathValidator::validate(const QString &path
|
|||
if (pathComponents.empty())
|
||||
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;
|
||||
|
||||
QStringRef componentPath(&path, 0, pathComponents[i].position() + pathComponents[i].size());
|
||||
m_lastTestResult = testPath(componentPath, false);
|
||||
if (m_lastTestResult != TestResult::OK) {
|
||||
if (m_lastTestResult != TestResult::OK)
|
||||
{
|
||||
m_lastTestedPath = componentPath.toString();
|
||||
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()
|
||||
+ pathComponents[lastComponentToTest].size());
|
||||
m_lastTestResult = testPath(componentPath, finalPath);
|
||||
if (m_lastTestResult != TestResult::OK) {
|
||||
if (m_lastTestResult != TestResult::OK)
|
||||
{
|
||||
m_lastTestedPath = componentPath.toString();
|
||||
return strict ? QValidator::Invalid : QValidator::Intermediate;
|
||||
}
|
||||
|
@ -172,7 +176,8 @@ Private::FileSystemPathValidator::testPath(const QStringRef &path, bool pathIsCo
|
|||
if ((!pathIsComplete || m_directoriesOnly) && !fi.isDir())
|
||||
return TestResult::NotADir;
|
||||
|
||||
if (pathIsComplete) {
|
||||
if (pathIsComplete)
|
||||
{
|
||||
if (!m_directoriesOnly && fi.isDir())
|
||||
return TestResult::NotAFile;
|
||||
|
||||
|
@ -249,27 +254,33 @@ QWidget *Private::FileLineEdit::widget()
|
|||
void Private::FileLineEdit::keyPressEvent(QKeyEvent *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());
|
||||
showCompletionPopup();
|
||||
}
|
||||
|
||||
auto *validator = qobject_cast<const FileSystemPathValidator *>(this->validator());
|
||||
if (validator) {
|
||||
if (validator)
|
||||
{
|
||||
FileSystemPathValidator::TestResult lastTestResult = validator->lastTestResult();
|
||||
QValidator::State lastState = validator->lastValidationState();
|
||||
if (lastTestResult == FileSystemPathValidator::TestResult::OK) {
|
||||
if (lastTestResult == FileSystemPathValidator::TestResult::OK)
|
||||
{
|
||||
delete m_warningAction;
|
||||
m_warningAction = nullptr;
|
||||
}
|
||||
else {
|
||||
if (!m_warningAction) {
|
||||
else
|
||||
{
|
||||
if (!m_warningAction)
|
||||
{
|
||||
m_warningAction = new QAction(this);
|
||||
addAction(m_warningAction, QLineEdit::TrailingPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_warningAction) {
|
||||
if (m_warningAction)
|
||||
{
|
||||
if (lastState == QValidator::Invalid)
|
||||
m_warningAction->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical));
|
||||
else if (lastState == QValidator::Intermediate)
|
||||
|
@ -284,7 +295,8 @@ void Private::FileLineEdit::contextMenuEvent(QContextMenuEvent *event)
|
|||
QMenu *menu = createStandardContextMenu();
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (m_browseAction) {
|
||||
if (m_browseAction)
|
||||
{
|
||||
menu->addSeparator();
|
||||
menu->addAction(m_browseAction);
|
||||
}
|
||||
|
@ -301,7 +313,8 @@ void Private::FileLineEdit::showCompletionPopup()
|
|||
QString Private::FileLineEdit::warningText(FileSystemPathValidator::TestResult r)
|
||||
{
|
||||
using TestResult = FileSystemPathValidator::TestResult;
|
||||
switch (r) {
|
||||
switch (r)
|
||||
{
|
||||
case TestResult::DoesNotExist:
|
||||
return tr("'%1' does not exist");
|
||||
case TestResult::NotADir:
|
||||
|
|
|
@ -68,7 +68,8 @@ IPSubnetWhitelistOptionsDialog::~IPSubnetWhitelistOptionsDialog()
|
|||
|
||||
void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
|
||||
{
|
||||
if (m_modified) {
|
||||
if (m_modified)
|
||||
{
|
||||
// save to session
|
||||
QStringList subnets;
|
||||
// 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);
|
||||
QDialog::accept();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QDialog::reject();
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +88,8 @@ void IPSubnetWhitelistOptionsDialog::on_buttonWhitelistIPSubnet_clicked()
|
|||
{
|
||||
bool ok = false;
|
||||
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."));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ void LineEdit::resizeEvent(QResizeEvent *e)
|
|||
|
||||
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();
|
||||
}
|
||||
QLineEdit::keyPressEvent(event);
|
||||
|
|
|
@ -97,7 +97,8 @@ QVariant BaseLogModel::data(const QModelIndex &index, const int role) const
|
|||
return {};
|
||||
|
||||
const Message &message = m_messages[messageIndex];
|
||||
switch (role) {
|
||||
switch (role)
|
||||
{
|
||||
case TimeRole:
|
||||
return message.time();
|
||||
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
|
||||
// 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;
|
||||
beginRemoveRows(QModelIndex(), lastMessage, lastMessage);
|
||||
m_messages.pop_back();
|
||||
|
@ -138,7 +140,8 @@ void BaseLogModel::reset()
|
|||
|
||||
LogMessageModel::LogMessageModel(QObject *parent)
|
||||
: BaseLogModel(parent)
|
||||
, m_foregroundForMessageTypes {
|
||||
, m_foregroundForMessageTypes
|
||||
{
|
||||
{Log::NORMAL, UIThemeManager::instance()->getColor(QLatin1String("Log.Normal"), QApplication::palette().color(QPalette::WindowText))},
|
||||
{Log::INFO, UIThemeManager::instance()->getColor(QLatin1String("Log.Info"), Qt::blue)},
|
||||
{Log::WARNING, UIThemeManager::instance()->getColor(QLatin1String("Log.Warning"), QColor {255, 165, 0})}, // orange
|
||||
|
|
|
@ -281,8 +281,10 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
for (QAction *action : asConst(m_ui->toolBar->actions())) {
|
||||
if (action->isSeparator()) {
|
||||
for (QAction *action : asConst(m_ui->toolBar->actions()))
|
||||
{
|
||||
if (action->isSeparator())
|
||||
{
|
||||
QWidget *spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
spacer->setMinimumWidth(16);
|
||||
|
@ -406,30 +408,38 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
readSettings();
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
if (m_systrayIcon) {
|
||||
if (!(pref->startMinimized() || m_uiLocked)) {
|
||||
if (m_systrayIcon)
|
||||
{
|
||||
if (!(pref->startMinimized() || m_uiLocked))
|
||||
{
|
||||
show();
|
||||
activateWindow();
|
||||
raise();
|
||||
}
|
||||
else if (pref->startMinimized()) {
|
||||
else if (pref->startMinimized())
|
||||
{
|
||||
showMinimized();
|
||||
if (pref->minimizeToTray()) {
|
||||
if (pref->minimizeToTray())
|
||||
{
|
||||
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."));
|
||||
pref->setMinimizeToTrayNotified(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
#endif
|
||||
// Make sure the Window is visible if we don't have a tray icon
|
||||
if (pref->startMinimized()) {
|
||||
if (pref->startMinimized())
|
||||
{
|
||||
showMinimized();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
show();
|
||||
activateWindow();
|
||||
raise();
|
||||
|
@ -456,14 +466,17 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
qDebug("GUI Built");
|
||||
#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"),
|
||||
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::setMagnetLinkAssoc(true);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
pref->setNeverCheckFileAssoc();
|
||||
}
|
||||
}
|
||||
|
@ -567,7 +580,8 @@ void MainWindow::addToolbarContextMenu()
|
|||
const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
|
||||
if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
|
||||
m_ui->toolBar->setToolButtonStyle(buttonStyle);
|
||||
switch (buttonStyle) {
|
||||
switch (buttonStyle)
|
||||
{
|
||||
case Qt::ToolButtonIconOnly:
|
||||
iconsOnly->setChecked(true);
|
||||
break;
|
||||
|
@ -635,7 +649,8 @@ bool MainWindow::defineUILockPassword()
|
|||
if (!ok)
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
|
@ -657,7 +672,8 @@ void MainWindow::on_actionLock_triggered()
|
|||
Preferences *const pref = Preferences::instance();
|
||||
|
||||
// Check if there is a password
|
||||
if (pref->getUILockPassword().isEmpty()) {
|
||||
if (pref->getUILockPassword().isEmpty())
|
||||
{
|
||||
if (!defineUILockPassword())
|
||||
return;
|
||||
}
|
||||
|
@ -676,9 +692,11 @@ void MainWindow::handleRSSUnreadCountUpdated(int count)
|
|||
|
||||
void MainWindow::displayRSSTab(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
if (enable)
|
||||
{
|
||||
// RSS tab
|
||||
if (!m_rssWidget) {
|
||||
if (!m_rssWidget)
|
||||
{
|
||||
m_rssWidget = new RSSWidget(m_tabs);
|
||||
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
|
||||
#ifdef Q_OS_MACOS
|
||||
|
@ -689,7 +707,8 @@ void MainWindow::displayRSSTab(bool enable)
|
|||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
delete m_rssWidget;
|
||||
}
|
||||
}
|
||||
|
@ -714,9 +733,11 @@ void MainWindow::showFilterContextMenu(const QPoint &)
|
|||
void MainWindow::displaySearchTab(bool enable)
|
||||
{
|
||||
Preferences::instance()->setSearchEnabled(enable);
|
||||
if (enable) {
|
||||
if (enable)
|
||||
{
|
||||
// RSS tab
|
||||
if (!m_searchWidget) {
|
||||
if (!m_searchWidget)
|
||||
{
|
||||
m_searchWidget = new SearchWidget(this);
|
||||
m_tabs->insertTab(1, m_searchWidget,
|
||||
#ifndef Q_OS_MACOS
|
||||
|
@ -725,7 +746,8 @@ void MainWindow::displaySearchTab(bool enable)
|
|||
tr("Search"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
delete m_searchWidget;
|
||||
}
|
||||
}
|
||||
|
@ -751,7 +773,8 @@ void MainWindow::tabChanged(int newTab)
|
|||
Q_UNUSED(newTab);
|
||||
// We cannot rely on the index newTab
|
||||
// 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");
|
||||
m_propertiesWidget->loadDynamicData();
|
||||
m_searchFilterAction->setVisible(true);
|
||||
|
@ -759,7 +782,8 @@ void MainWindow::tabChanged(int newTab)
|
|||
}
|
||||
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");
|
||||
m_searchWidget->giveFocusToSearchInput();
|
||||
}
|
||||
|
@ -815,8 +839,10 @@ void MainWindow::readSettings()
|
|||
|
||||
void MainWindow::balloonClicked()
|
||||
{
|
||||
if (isHidden()) {
|
||||
if (m_uiLocked) {
|
||||
if (isHidden())
|
||||
{
|
||||
if (m_uiLocked)
|
||||
{
|
||||
// Ask for UI lock password
|
||||
if (!unlockUI())
|
||||
return;
|
||||
|
@ -916,7 +942,8 @@ void MainWindow::displayTransferTab() const
|
|||
|
||||
void MainWindow::displaySearchTab()
|
||||
{
|
||||
if (!m_searchWidget) {
|
||||
if (!m_searchWidget)
|
||||
{
|
||||
m_ui->actionSearchWidget->setChecked(true);
|
||||
displaySearchTab(true);
|
||||
}
|
||||
|
@ -926,7 +953,8 @@ void MainWindow::displaySearchTab()
|
|||
|
||||
void MainWindow::displayRSSTab()
|
||||
{
|
||||
if (!m_rssWidget) {
|
||||
if (!m_rssWidget)
|
||||
{
|
||||
m_ui->actionRSSReader->setChecked(true);
|
||||
displayRSSTab(true);
|
||||
}
|
||||
|
@ -936,7 +964,8 @@ void MainWindow::displayRSSTab()
|
|||
|
||||
void MainWindow::displayExecutionLogTab()
|
||||
{
|
||||
if (!m_executionLog) {
|
||||
if (!m_executionLog)
|
||||
{
|
||||
m_ui->actionExecutionLogs->setChecked(true);
|
||||
on_actionExecutionLogs_triggered(true);
|
||||
}
|
||||
|
@ -988,7 +1017,8 @@ void MainWindow::on_actionSetGlobalUploadLimit_triggered()
|
|||
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
||||
this, &ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit());
|
||||
|
||||
if (ok) {
|
||||
if (ok)
|
||||
{
|
||||
qDebug("Setting global upload rate limit to %.1fKb/s", newLimit / 1024.);
|
||||
session->setUploadSpeedLimit(newLimit);
|
||||
}
|
||||
|
@ -1003,7 +1033,8 @@ void MainWindow::on_actionSetGlobalDownloadLimit_triggered()
|
|||
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
||||
this, &ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit());
|
||||
|
||||
if (ok) {
|
||||
if (ok)
|
||||
{
|
||||
qDebug("Setting global download rate limit to %.1fKb/s", newLimit / 1024.);
|
||||
session->setDownloadSpeedLimit(newLimit);
|
||||
}
|
||||
|
@ -1059,7 +1090,8 @@ bool MainWindow::unlockUI()
|
|||
Preferences *const pref = Preferences::instance();
|
||||
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
|
@ -1085,9 +1117,11 @@ void MainWindow::notifyOfUpdate(const QString &)
|
|||
// Toggle Main window visibility
|
||||
void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
switch (reason)
|
||||
{
|
||||
case QSystemTrayIcon::Trigger:
|
||||
if (isHidden()) {
|
||||
if (isHidden())
|
||||
{
|
||||
if (m_uiLocked && !unlockUI()) // Ask for UI lock password
|
||||
return;
|
||||
|
||||
|
@ -1099,7 +1133,8 @@ void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason
|
|||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
hide();
|
||||
}
|
||||
break;
|
||||
|
@ -1138,7 +1173,8 @@ void MainWindow::showEvent(QShowEvent *e)
|
|||
e->accept();
|
||||
|
||||
// Make sure the window is initially centered
|
||||
if (!m_posInitialized) {
|
||||
if (!m_posInitialized)
|
||||
{
|
||||
move(Utils::Gui::screenCenter(this));
|
||||
m_posInitialized = true;
|
||||
}
|
||||
|
@ -1146,14 +1182,17 @@ void MainWindow::showEvent(QShowEvent *e)
|
|||
|
||||
void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->matches(QKeySequence::Paste)) {
|
||||
if (event->matches(QKeySequence::Paste))
|
||||
{
|
||||
const QMimeData *mimeData {QGuiApplication::clipboard()->mimeData()};
|
||||
|
||||
if (mimeData->hasText()) {
|
||||
if (mimeData->hasText())
|
||||
{
|
||||
const bool useTorrentAdditionDialog {AddNewTorrentDialog::isEnabled()};
|
||||
const QStringList lines {mimeData->text().split('\n', QString::SkipEmptyParts)};
|
||||
|
||||
for (QString line : lines) {
|
||||
for (QString line : lines)
|
||||
{
|
||||
line = line.trimmed();
|
||||
|
||||
if (!isTorrentLink(line))
|
||||
|
@ -1177,17 +1216,20 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
|||
{
|
||||
Preferences *const pref = Preferences::instance();
|
||||
#ifdef Q_OS_MACOS
|
||||
if (!m_forceExit) {
|
||||
if (!m_forceExit)
|
||||
{
|
||||
hide();
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
const bool goToSystrayOnExit = pref->closeToTray();
|
||||
if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden()) {
|
||||
if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden())
|
||||
{
|
||||
e->ignore();
|
||||
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."));
|
||||
pref->setCloseToTrayNotified(true);
|
||||
}
|
||||
|
@ -1195,8 +1237,10 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
|||
}
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) {
|
||||
if (e->spontaneous() || m_forceExit) {
|
||||
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents())
|
||||
{
|
||||
if (e->spontaneous() || m_forceExit)
|
||||
{
|
||||
if (!isVisible())
|
||||
show();
|
||||
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);
|
||||
confirmBox.setDefaultButton(noBtn);
|
||||
confirmBox.exec();
|
||||
if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn)) {
|
||||
if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
|
||||
{
|
||||
// Cancel exit
|
||||
e->ignore();
|
||||
m_forceExit = false;
|
||||
|
@ -1242,7 +1287,8 @@ void MainWindow::on_actionCreateTorrent_triggered()
|
|||
|
||||
void MainWindow::createTorrentTriggered(const QString &path)
|
||||
{
|
||||
if (m_createTorrentDlg) {
|
||||
if (m_createTorrentDlg)
|
||||
{
|
||||
m_createTorrentDlg->updateInputPath(path);
|
||||
m_createTorrentDlg->activateWindow();
|
||||
}
|
||||
|
@ -1253,29 +1299,37 @@ void MainWindow::createTorrentTriggered(const QString &path)
|
|||
bool MainWindow::event(QEvent *e)
|
||||
{
|
||||
#ifndef Q_OS_MACOS
|
||||
switch (e->type()) {
|
||||
case QEvent::WindowStateChange: {
|
||||
switch (e->type())
|
||||
{
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
qDebug("Window change event");
|
||||
// Now check to see if the window is minimised
|
||||
if (isMinimized()) {
|
||||
if (isMinimized())
|
||||
{
|
||||
qDebug("minimisation");
|
||||
Preferences *const pref = Preferences::instance();
|
||||
if (m_systrayIcon && pref->minimizeToTray()) {
|
||||
if (m_systrayIcon && pref->minimizeToTray())
|
||||
{
|
||||
qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
|
||||
// Check if there is a modal window
|
||||
bool hasModalWindow = false;
|
||||
for (QWidget *widget : asConst(QApplication::allWidgets())) {
|
||||
if (widget->isModal()) {
|
||||
for (QWidget *widget : asConst(QApplication::allWidgets()))
|
||||
{
|
||||
if (widget->isModal())
|
||||
{
|
||||
hasModalWindow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Iconify if there is no modal window
|
||||
if (!hasModalWindow) {
|
||||
if (!hasModalWindow)
|
||||
{
|
||||
qDebug("Minimize to Tray enabled, hiding!");
|
||||
e->ignore();
|
||||
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."));
|
||||
pref->setMinimizeToTrayNotified(true);
|
||||
}
|
||||
|
@ -1285,7 +1339,8 @@ bool MainWindow::event(QEvent *e)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::ToolBarChange: {
|
||||
case QEvent::ToolBarChange:
|
||||
{
|
||||
qDebug("MAC: Received a toolbar change event!");
|
||||
bool ret = QMainWindow::event(e);
|
||||
|
||||
|
@ -1309,8 +1364,10 @@ void MainWindow::dropEvent(QDropEvent *event)
|
|||
|
||||
// remove scheme
|
||||
QStringList files;
|
||||
if (event->mimeData()->hasUrls()) {
|
||||
for (const QUrl &url : asConst(event->mimeData()->urls())) {
|
||||
if (event->mimeData()->hasUrls())
|
||||
{
|
||||
for (const QUrl &url : asConst(event->mimeData()->urls()))
|
||||
{
|
||||
if (url.isEmpty())
|
||||
continue;
|
||||
|
||||
|
@ -1319,13 +1376,15 @@ void MainWindow::dropEvent(QDropEvent *event)
|
|||
: url.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
files = event->mimeData()->text().split('\n');
|
||||
}
|
||||
|
||||
// differentiate ".torrent" files/links & magnet links from others
|
||||
QStringList torrentFiles, otherFiles;
|
||||
for (const QString &file : asConst(files)) {
|
||||
for (const QString &file : asConst(files))
|
||||
{
|
||||
if (isTorrentLink(file))
|
||||
torrentFiles << file;
|
||||
else
|
||||
|
@ -1334,7 +1393,8 @@ void MainWindow::dropEvent(QDropEvent *event)
|
|||
|
||||
// Download torrents
|
||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
||||
for (const QString &file : asConst(torrentFiles)) {
|
||||
for (const QString &file : asConst(torrentFiles))
|
||||
{
|
||||
if (useTorrentAdditionDialog)
|
||||
AddNewTorrentDialog::show(file, this);
|
||||
else
|
||||
|
@ -1343,7 +1403,8 @@ void MainWindow::dropEvent(QDropEvent *event)
|
|||
if (!torrentFiles.isEmpty()) return;
|
||||
|
||||
// Create torrent
|
||||
for (const QString &file : asConst(otherFiles)) {
|
||||
for (const QString &file : asConst(otherFiles))
|
||||
{
|
||||
createTorrentTriggered(file);
|
||||
|
||||
// currently only handle the first entry
|
||||
|
@ -1370,7 +1431,8 @@ static bool dockClickHandler(id self, SEL cmd, ...)
|
|||
Q_UNUSED(self)
|
||||
Q_UNUSED(cmd)
|
||||
|
||||
if (dockMainWindowHandle && !dockMainWindowHandle->isVisible()) {
|
||||
if (dockMainWindowHandle && !dockMainWindowHandle->isVisible())
|
||||
{
|
||||
dockMainWindowHandle->activate();
|
||||
}
|
||||
|
||||
|
@ -1407,7 +1469,8 @@ void MainWindow::on_actionOpen_triggered()
|
|||
|
||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
||||
|
||||
for (const QString &file : pathsList) {
|
||||
for (const QString &file : pathsList)
|
||||
{
|
||||
if (useTorrentAdditionDialog)
|
||||
AddNewTorrentDialog::show(file, this);
|
||||
else
|
||||
|
@ -1422,7 +1485,8 @@ void MainWindow::on_actionOpen_triggered()
|
|||
|
||||
void MainWindow::activate()
|
||||
{
|
||||
if (!m_uiLocked || unlockUI()) {
|
||||
if (!m_uiLocked || unlockUI())
|
||||
{
|
||||
show();
|
||||
activateWindow();
|
||||
raise();
|
||||
|
@ -1436,11 +1500,13 @@ void MainWindow::optionsSaved()
|
|||
|
||||
void MainWindow::showStatusBar(bool show)
|
||||
{
|
||||
if (!show) {
|
||||
if (!show)
|
||||
{
|
||||
// Remove status bar
|
||||
setStatusBar(nullptr);
|
||||
}
|
||||
else if (!m_statusBar) {
|
||||
else if (!m_statusBar)
|
||||
{
|
||||
// Create status bar
|
||||
m_statusBar = new StatusBar;
|
||||
connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
|
||||
|
@ -1458,26 +1524,33 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
#else
|
||||
const bool newSystrayIntegration = pref->systrayIntegration();
|
||||
m_ui->actionLock->setVisible(newSystrayIntegration);
|
||||
if (newSystrayIntegration != (m_systrayIcon != nullptr)) {
|
||||
if (newSystrayIntegration) {
|
||||
if (newSystrayIntegration != (m_systrayIcon != nullptr))
|
||||
{
|
||||
if (newSystrayIntegration)
|
||||
{
|
||||
// create the trayicon
|
||||
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||
if (!configureSession) { // Program startup
|
||||
if (!QSystemTrayIcon::isSystemTrayAvailable())
|
||||
{
|
||||
if (!configureSession)
|
||||
{ // Program startup
|
||||
m_systrayCreator = new QTimer(this);
|
||||
connect(m_systrayCreator.data(), &QTimer::timeout, this, &MainWindow::createSystrayDelayed);
|
||||
m_systrayCreator->setSingleShot(true);
|
||||
m_systrayCreator->start(2000);
|
||||
qDebug("Info: System tray is unavailable, trying again later.");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
qDebug("Warning: System tray is unavailable.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
createTrayIcon();
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Destroy trayicon
|
||||
delete m_systrayIcon;
|
||||
delete m_trayIconMenu;
|
||||
|
@ -1488,10 +1561,12 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
m_systrayIcon->setIcon(getSystrayIcon());
|
||||
#endif
|
||||
// General
|
||||
if (pref->isToolbarDisplayed()) {
|
||||
if (pref->isToolbarDisplayed())
|
||||
{
|
||||
m_ui->toolBar->setVisible(true);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Clear search filter before hiding the top toolbar
|
||||
m_searchFilter->clear();
|
||||
m_ui->toolBar->setVisible(false);
|
||||
|
@ -1499,13 +1574,16 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
|
||||
showStatusBar(pref->isStatusbarDisplayed());
|
||||
|
||||
if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding()) {
|
||||
if (!m_preventTimer->isActive()) {
|
||||
if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding())
|
||||
{
|
||||
if (!m_preventTimer->isActive())
|
||||
{
|
||||
updatePowerManagementState();
|
||||
m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_preventTimer->stop();
|
||||
m_pwr->setActivityState(false);
|
||||
}
|
||||
|
@ -1516,8 +1594,10 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
|
||||
|
||||
// Queueing System
|
||||
if (BitTorrent::Session::instance()->isQueueingSystemEnabled()) {
|
||||
if (!m_ui->actionDecreaseQueuePos->isVisible()) {
|
||||
if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
|
||||
{
|
||||
if (!m_ui->actionDecreaseQueuePos->isVisible())
|
||||
{
|
||||
m_transferListWidget->hideQueuePosColumn(false);
|
||||
m_ui->actionDecreaseQueuePos->setVisible(true);
|
||||
m_ui->actionIncreaseQueuePos->setVisible(true);
|
||||
|
@ -1529,8 +1609,10 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
m_queueSeparatorMenu->setVisible(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (m_ui->actionDecreaseQueuePos->isVisible()) {
|
||||
else
|
||||
{
|
||||
if (m_ui->actionDecreaseQueuePos->isVisible())
|
||||
{
|
||||
m_transferListWidget->hideQueuePosColumn(true);
|
||||
m_ui->actionDecreaseQueuePos->setVisible(false);
|
||||
m_ui->actionIncreaseQueuePos->setVisible(false);
|
||||
|
@ -1547,11 +1629,13 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||
m_propertiesWidget->reloadPreferences();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) {
|
||||
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled)
|
||||
{
|
||||
m_wasUpdateCheckEnabled = true;
|
||||
checkProgramUpdate();
|
||||
}
|
||||
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled) {
|
||||
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled)
|
||||
{
|
||||
m_wasUpdateCheckEnabled = false;
|
||||
m_programUpdateTimer->stop();
|
||||
}
|
||||
|
@ -1566,15 +1650,18 @@ void MainWindow::reloadSessionStats()
|
|||
|
||||
// update global information
|
||||
#ifdef Q_OS_MACOS
|
||||
if (status.payloadDownloadRate > 0) {
|
||||
if (status.payloadDownloadRate > 0)
|
||||
{
|
||||
QtMac::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
|
||||
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
|
||||
}
|
||||
else if (!QtMac::badgeLabelText().isEmpty()) {
|
||||
else if (!QtMac::badgeLabelText().isEmpty())
|
||||
{
|
||||
QtMac::setBadgeLabelText("");
|
||||
}
|
||||
#else
|
||||
if (m_systrayIcon) {
|
||||
if (m_systrayIcon)
|
||||
{
|
||||
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("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
|
||||
|
||||
if (m_displaySpeedInTitle) {
|
||||
if (m_displaySpeedInTitle)
|
||||
{
|
||||
setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
|
||||
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
|
||||
, Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
|
||||
|
@ -1592,7 +1680,8 @@ void MainWindow::reloadSessionStats()
|
|||
|
||||
void MainWindow::reloadTorrentStats(const QVector<BitTorrent::TorrentHandle *> &torrents)
|
||||
{
|
||||
if (currentTabWidget() == m_transferListWidget) {
|
||||
if (currentTabWidget() == m_transferListWidget)
|
||||
{
|
||||
if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
|
||||
m_propertiesWidget->loadDynamicData();
|
||||
}
|
||||
|
@ -1638,7 +1727,8 @@ void MainWindow::showNotificationBaloon(const QString &title, const QString &msg
|
|||
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
||||
{
|
||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
||||
for (const QString &url : urlList) {
|
||||
for (const QString &url : urlList)
|
||||
{
|
||||
if (useTorrentAdditionDialog)
|
||||
AddNewTorrentDialog::show(url, this);
|
||||
else
|
||||
|
@ -1656,19 +1746,23 @@ void MainWindow::downloadFromURLList(const QStringList &urlList)
|
|||
void MainWindow::createSystrayDelayed()
|
||||
{
|
||||
static int timeout = 20;
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable())
|
||||
{
|
||||
// Ok, systray integration is now supported
|
||||
// Create systray icon
|
||||
createTrayIcon();
|
||||
delete m_systrayCreator;
|
||||
}
|
||||
else {
|
||||
if (timeout) {
|
||||
else
|
||||
{
|
||||
if (timeout)
|
||||
{
|
||||
// Retry a bit later
|
||||
m_systrayCreator->start(2000);
|
||||
--timeout;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Timed out, apparently system really does not
|
||||
// support systray icon
|
||||
delete m_systrayCreator;
|
||||
|
@ -1780,11 +1874,13 @@ void MainWindow::on_actionRSSReader_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();
|
||||
|
||||
// Not installed
|
||||
if (!pyInfo.isValid()) {
|
||||
if (!pyInfo.isValid())
|
||||
{
|
||||
m_ui->actionSearchWidget->setChecked(false);
|
||||
Preferences::instance()->setSearchEnabled(false);
|
||||
|
||||
|
@ -1802,7 +1898,8 @@ void MainWindow::on_actionSearchWidget_triggered()
|
|||
}
|
||||
|
||||
// Check version requirement
|
||||
if (!pyInfo.isSupportedVersion()) {
|
||||
if (!pyInfo.isSupportedVersion())
|
||||
{
|
||||
m_ui->actionSearchWidget->setChecked(false);
|
||||
Preferences::instance()->setSearchEnabled(false);
|
||||
|
||||
|
@ -1839,7 +1936,8 @@ void MainWindow::on_actionSearchWidget_triggered()
|
|||
// an url
|
||||
void MainWindow::on_actionDownloadFromURL_triggered()
|
||||
{
|
||||
if (!m_downloadFromURLDialog) {
|
||||
if (!m_downloadFromURLDialog)
|
||||
{
|
||||
m_downloadFromURLDialog = new DownloadFromURLDialog(this);
|
||||
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)
|
||||
{
|
||||
QMessageBox::StandardButton answer = QMessageBox::Yes;
|
||||
if (updateAvailable) {
|
||||
if (updateAvailable)
|
||||
{
|
||||
answer = QMessageBox::question(this, tr("qBittorrent Update Available")
|
||||
, tr("A new version is available.") + "<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..."))
|
||||
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
if (answer == QMessageBox::Yes)
|
||||
{
|
||||
// The user want to update, let's download the update
|
||||
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
|
||||
updater->updateProgram();
|
||||
}
|
||||
}
|
||||
else if (invokedByUser) {
|
||||
else if (invokedByUser)
|
||||
{
|
||||
QMessageBox::information(this, tr("Already Using the Latest qBittorrent 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)
|
||||
{
|
||||
if (checked) {
|
||||
if (checked)
|
||||
{
|
||||
Q_ASSERT(!m_executionLog);
|
||||
m_executionLog = new ExecutionLogWidget(static_cast<Log::MsgType>(executionLogMsgTypes()), m_tabs);
|
||||
#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"));
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
delete m_executionLog;
|
||||
}
|
||||
|
||||
|
@ -1997,7 +2100,8 @@ QIcon MainWindow::getSystrayIcon() const
|
|||
const TrayIcon::Style style = Preferences::instance()->trayIconStyle();
|
||||
// on Linux we use theme icons, and icons from resources everywhere else
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||
switch (style) {
|
||||
switch (style)
|
||||
{
|
||||
case TrayIcon::NORMAL:
|
||||
return QIcon::fromTheme(QLatin1String("qbittorrent-tray"));
|
||||
case TrayIcon::MONO_DARK:
|
||||
|
@ -2008,7 +2112,8 @@ QIcon MainWindow::getSystrayIcon() const
|
|||
break;
|
||||
}
|
||||
#else
|
||||
switch (style) {
|
||||
switch (style)
|
||||
{
|
||||
case TrayIcon::NORMAL:
|
||||
return UIThemeManager::instance()->getIcon(QLatin1String("qbittorrent-tray"));
|
||||
case TrayIcon::MONO_DARK:
|
||||
|
@ -2057,7 +2162,8 @@ void MainWindow::installPython()
|
|||
|
||||
void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
if (result.status != Net::DownloadStatus::Success) {
|
||||
if (result.status != Net::DownloadStatus::Success)
|
||||
{
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
QMessageBox::warning(
|
||||
this, tr("Download error")
|
||||
|
@ -2084,7 +2190,8 @@ void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
|
|||
Utils::Fs::forceRemove(result.filePath + ".exe");
|
||||
|
||||
// Reload search engine
|
||||
if (Utils::ForeignApps::pythonInfo().isSupportedVersion()) {
|
||||
if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
|
||||
{
|
||||
m_ui->actionSearchWidget->setChecked(true);
|
||||
displaySearchTab(true);
|
||||
}
|
||||
|
|
|
@ -84,7 +84,8 @@ namespace
|
|||
|
||||
QString languageToLocalizedString(const QLocale &locale)
|
||||
{
|
||||
switch (locale.language()) {
|
||||
switch (locale.language())
|
||||
{
|
||||
case QLocale::Arabic: return QString::fromUtf8(C_LOCALE_ARABIC);
|
||||
case QLocale::Armenian: return QString::fromUtf8(C_LOCALE_ARMENIAN);
|
||||
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::Catalan: return QString::fromUtf8(C_LOCALE_CATALAN);
|
||||
case QLocale::Chinese:
|
||||
switch (locale.country()) {
|
||||
switch (locale.country())
|
||||
{
|
||||
case QLocale::China: return QString::fromUtf8(C_LOCALE_CHINESE_SIMPLIFIED);
|
||||
case QLocale::HongKong: return QString::fromUtf8(C_LOCALE_CHINESE_TRADITIONAL_HK);
|
||||
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::Dutch: return QString::fromUtf8(C_LOCALE_DUTCH);
|
||||
case QLocale::English:
|
||||
switch (locale.country()) {
|
||||
switch (locale.country())
|
||||
{
|
||||
case QLocale::Australia: return QString::fromUtf8(C_LOCALE_ENGLISH_AUSTRALIA);
|
||||
case QLocale::UnitedKingdom: return QString::fromUtf8(C_LOCALE_ENGLISH_UNITEDKINGDOM);
|
||||
default: return QString::fromUtf8(C_LOCALE_ENGLISH);
|
||||
|
@ -195,7 +198,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
int maxHeight = -1;
|
||||
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());
|
||||
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));
|
||||
m_ui->tabSelection->item(i)->setSizeHint(size);
|
||||
}
|
||||
|
@ -225,8 +229,10 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
m_ui->hsplitter->setCollapsible(1, false);
|
||||
// Get apply button in button box
|
||||
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons();
|
||||
for (QAbstractButton *button : buttons) {
|
||||
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) {
|
||||
for (QAbstractButton *button : buttons)
|
||||
{
|
||||
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
|
||||
{
|
||||
m_applyButton = button;
|
||||
break;
|
||||
}
|
||||
|
@ -263,7 +269,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||
m_ui->checkShowSystray->setVisible(false);
|
||||
#else
|
||||
// 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->setEnabled(false);
|
||||
m_ui->labelTrayIconStyle->setVisible(false);
|
||||
|
@ -567,19 +574,23 @@ void OptionsDialog::initializeLanguageCombo()
|
|||
// List language files
|
||||
const QDir langDir(":/lang");
|
||||
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_"
|
||||
localeStr.chop(3); // Remove ".qm"
|
||||
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.
|
||||
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.
|
||||
languageName = QString::fromUtf8(C_LOCALE_LATGALIAN);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QLocale locale(localeStr);
|
||||
languageName = languageToLocalizedString(locale);
|
||||
}
|
||||
|
@ -634,7 +645,8 @@ void OptionsDialog::saveWindowState() const
|
|||
pref->setPrefSize(size());
|
||||
|
||||
// Splitter size
|
||||
const QStringList sizesStr = {
|
||||
const QStringList sizesStr =
|
||||
{
|
||||
QString::number(m_ui->hsplitter->sizes().first()),
|
||||
QString::number(m_ui->hsplitter->sizes().last())
|
||||
};
|
||||
|
@ -647,7 +659,8 @@ void OptionsDialog::saveOptions()
|
|||
Preferences *const pref = Preferences::instance();
|
||||
// Load the translation
|
||||
QString locale = getLocale();
|
||||
if (pref->getLocale() != locale) {
|
||||
if (pref->getLocale() != locale)
|
||||
{
|
||||
auto *translator = new QTranslator;
|
||||
if (translator->load(QLatin1String(":/lang/qbittorrent_") + locale))
|
||||
qDebug("%s locale recognized, using translation.", qUtf8Printable(locale));
|
||||
|
@ -689,12 +702,14 @@ void OptionsDialog::saveOptions()
|
|||
Preferences::setMagnetLinkAssoc(m_ui->checkAssociateMagnetLinks->isChecked());
|
||||
#endif
|
||||
#ifdef Q_OS_MACOS
|
||||
if (m_ui->checkAssociateTorrents->isChecked()) {
|
||||
if (m_ui->checkAssociateTorrents->isChecked())
|
||||
{
|
||||
Preferences::setTorrentFileAssoc();
|
||||
m_ui->checkAssociateTorrents->setChecked(Preferences::isTorrentFileAssocSet());
|
||||
m_ui->checkAssociateTorrents->setEnabled(!m_ui->checkAssociateTorrents->isChecked());
|
||||
}
|
||||
if (m_ui->checkAssociateMagnetLinks->isChecked()) {
|
||||
if (m_ui->checkAssociateMagnetLinks->isChecked())
|
||||
{
|
||||
Preferences::setMagnetLinkAssoc();
|
||||
m_ui->checkAssociateMagnetLinks->setChecked(Preferences::isMagnetLinkAssocSet());
|
||||
m_ui->checkAssociateMagnetLinks->setEnabled(!m_ui->checkAssociateMagnetLinks->isChecked());
|
||||
|
@ -810,7 +825,8 @@ void OptionsDialog::saveOptions()
|
|||
session->setGlobalMaxRatio(getMaxRatio());
|
||||
session->setGlobalMaxSeedingMinutes(getMaxSeedingMinutes());
|
||||
|
||||
const QVector<MaxRatioAction> actIndex = {
|
||||
const QVector<MaxRatioAction> actIndex =
|
||||
{
|
||||
Pause,
|
||||
Remove,
|
||||
DeleteFiles,
|
||||
|
@ -837,7 +853,8 @@ void OptionsDialog::saveOptions()
|
|||
// End Queueing system preferences
|
||||
// Web UI
|
||||
pref->setWebUiEnabled(isWebUiEnabled());
|
||||
if (isWebUiEnabled()) {
|
||||
if (isWebUiEnabled())
|
||||
{
|
||||
pref->setServerDomains(m_ui->textServerDomains->text());
|
||||
pref->setWebUiAddress(m_ui->textWebUiAddress->text());
|
||||
pref->setWebUiPort(m_ui->spinWebUiPort->value());
|
||||
|
@ -888,7 +905,8 @@ bool OptionsDialog::isIPFilteringEnabled() const
|
|||
|
||||
Net::ProxyType OptionsDialog::getProxyType() const
|
||||
{
|
||||
switch (m_ui->comboProxyType->currentIndex()) {
|
||||
switch (m_ui->comboProxyType->currentIndex())
|
||||
{
|
||||
case 1:
|
||||
return Net::ProxyType::SOCKS4;
|
||||
case 2:
|
||||
|
@ -927,7 +945,8 @@ void OptionsDialog::loadOptions()
|
|||
|
||||
#ifndef Q_OS_MACOS
|
||||
m_ui->checkShowSystray->setChecked(pref->systrayIntegration());
|
||||
if (m_ui->checkShowSystray->isChecked()) {
|
||||
if (m_ui->checkShowSystray->isChecked())
|
||||
{
|
||||
m_ui->checkMinimizeToSysTray->setChecked(pref->minimizeToTray());
|
||||
m_ui->checkCloseToSystray->setChecked(pref->closeToTray());
|
||||
m_ui->comboTrayIcon->setCurrentIndex(pref->trayIconStyle());
|
||||
|
@ -1001,12 +1020,14 @@ void OptionsDialog::loadOptions()
|
|||
m_ui->checkRecursiveDownload->setChecked(!pref->recursiveDownloadDisabled());
|
||||
|
||||
strValue = session->torrentExportDirectory();
|
||||
if (strValue.isEmpty()) {
|
||||
if (strValue.isEmpty())
|
||||
{
|
||||
// Disable
|
||||
m_ui->checkExportDir->setChecked(false);
|
||||
m_ui->textExportDir->setEnabled(false);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Enable
|
||||
m_ui->checkExportDir->setChecked(true);
|
||||
m_ui->textExportDir->setEnabled(true);
|
||||
|
@ -1014,12 +1035,14 @@ void OptionsDialog::loadOptions()
|
|||
}
|
||||
|
||||
strValue = session->finishedTorrentExportDirectory();
|
||||
if (strValue.isEmpty()) {
|
||||
if (strValue.isEmpty())
|
||||
{
|
||||
// Disable
|
||||
m_ui->checkExportDirFin->setChecked(false);
|
||||
m_ui->textExportDirFin->setEnabled(false);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Enable
|
||||
m_ui->checkExportDirFin->setChecked(true);
|
||||
m_ui->textExportDirFin->setEnabled(true);
|
||||
|
@ -1060,49 +1083,57 @@ void OptionsDialog::loadOptions()
|
|||
m_ui->spinPort->setDisabled(m_ui->checkRandomPort->isChecked());
|
||||
|
||||
intValue = session->maxConnections();
|
||||
if (intValue > 0) {
|
||||
if (intValue > 0)
|
||||
{
|
||||
// enable
|
||||
m_ui->checkMaxConnecs->setChecked(true);
|
||||
m_ui->spinMaxConnec->setEnabled(true);
|
||||
m_ui->spinMaxConnec->setValue(intValue);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// disable
|
||||
m_ui->checkMaxConnecs->setChecked(false);
|
||||
m_ui->spinMaxConnec->setEnabled(false);
|
||||
}
|
||||
intValue = session->maxConnectionsPerTorrent();
|
||||
if (intValue > 0) {
|
||||
if (intValue > 0)
|
||||
{
|
||||
// enable
|
||||
m_ui->checkMaxConnecsPerTorrent->setChecked(true);
|
||||
m_ui->spinMaxConnecPerTorrent->setEnabled(true);
|
||||
m_ui->spinMaxConnecPerTorrent->setValue(intValue);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// disable
|
||||
m_ui->checkMaxConnecsPerTorrent->setChecked(false);
|
||||
m_ui->spinMaxConnecPerTorrent->setEnabled(false);
|
||||
}
|
||||
intValue = session->maxUploads();
|
||||
if (intValue > 0) {
|
||||
if (intValue > 0)
|
||||
{
|
||||
// enable
|
||||
m_ui->checkMaxUploads->setChecked(true);
|
||||
m_ui->spinMaxUploads->setEnabled(true);
|
||||
m_ui->spinMaxUploads->setValue(intValue);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// disable
|
||||
m_ui->checkMaxUploads->setChecked(false);
|
||||
m_ui->spinMaxUploads->setEnabled(false);
|
||||
}
|
||||
intValue = session->maxUploadsPerTorrent();
|
||||
if (intValue > 0) {
|
||||
if (intValue > 0)
|
||||
{
|
||||
// enable
|
||||
m_ui->checkMaxUploadsPerTorrent->setChecked(true);
|
||||
m_ui->spinMaxUploadsPerTorrent->setEnabled(true);
|
||||
m_ui->spinMaxUploadsPerTorrent->setValue(intValue);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// disable
|
||||
m_ui->checkMaxUploadsPerTorrent->setChecked(false);
|
||||
m_ui->spinMaxUploadsPerTorrent->setEnabled(false);
|
||||
|
@ -1112,7 +1143,8 @@ void OptionsDialog::loadOptions()
|
|||
Net::ProxyConfiguration proxyConf = proxyConfigManager->proxyConfiguration();
|
||||
using Net::ProxyType;
|
||||
bool useProxyAuth = false;
|
||||
switch (proxyConf.type) {
|
||||
switch (proxyConf.type)
|
||||
{
|
||||
case ProxyType::SOCKS4:
|
||||
m_ui->comboProxyType->setCurrentIndex(1);
|
||||
break;
|
||||
|
@ -1185,32 +1217,37 @@ void OptionsDialog::loadOptions()
|
|||
m_ui->spinUploadRateForSlowTorrents->setValue(session->uploadRateForSlowTorrents());
|
||||
m_ui->spinSlowTorrentsInactivityTimer->setValue(session->slowTorrentsInactivityTimer());
|
||||
|
||||
if (session->globalMaxRatio() >= 0.) {
|
||||
if (session->globalMaxRatio() >= 0.)
|
||||
{
|
||||
// Enable
|
||||
m_ui->checkMaxRatio->setChecked(true);
|
||||
m_ui->spinMaxRatio->setEnabled(true);
|
||||
m_ui->comboRatioLimitAct->setEnabled(true);
|
||||
m_ui->spinMaxRatio->setValue(session->globalMaxRatio());
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Disable
|
||||
m_ui->checkMaxRatio->setChecked(false);
|
||||
m_ui->spinMaxRatio->setEnabled(false);
|
||||
}
|
||||
if (session->globalMaxSeedingMinutes() >= 0) {
|
||||
if (session->globalMaxSeedingMinutes() >= 0)
|
||||
{
|
||||
// Enable
|
||||
m_ui->checkMaxSeedingMinutes->setChecked(true);
|
||||
m_ui->spinMaxSeedingMinutes->setEnabled(true);
|
||||
m_ui->spinMaxSeedingMinutes->setValue(session->globalMaxSeedingMinutes());
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Disable
|
||||
m_ui->checkMaxSeedingMinutes->setChecked(false);
|
||||
m_ui->spinMaxSeedingMinutes->setEnabled(false);
|
||||
}
|
||||
m_ui->comboRatioLimitAct->setEnabled((session->globalMaxSeedingMinutes() >= 0) || (session->globalMaxRatio() >= 0.));
|
||||
|
||||
const QHash<MaxRatioAction, int> actIndex = {
|
||||
const QHash<MaxRatioAction, int> actIndex =
|
||||
{
|
||||
{Pause, 0},
|
||||
{Remove, 1},
|
||||
{DeleteFiles, 2},
|
||||
|
@ -1386,16 +1423,20 @@ int OptionsDialog::getMaxUploadsPerTorrent() const
|
|||
|
||||
void OptionsDialog::on_buttonBox_accepted()
|
||||
{
|
||||
if (m_applyButton->isEnabled()) {
|
||||
if (!schedTimesOk()) {
|
||||
if (m_applyButton->isEnabled())
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
}
|
||||
if (!webUIAuthenticationOk()) {
|
||||
if (!webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid()) {
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
|
@ -1409,16 +1450,20 @@ void OptionsDialog::on_buttonBox_accepted()
|
|||
|
||||
void OptionsDialog::applySettings(QAbstractButton *button)
|
||||
{
|
||||
if (button == m_applyButton) {
|
||||
if (!schedTimesOk()) {
|
||||
if (button == m_applyButton)
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
}
|
||||
if (!webUIAuthenticationOk()) {
|
||||
if (!webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid()) {
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
|
@ -1456,24 +1501,28 @@ void OptionsDialog::toggleComboRatioLimitAct()
|
|||
|
||||
void OptionsDialog::enableProxy(const int index)
|
||||
{
|
||||
if (index >= 1) { // Any proxy type is used
|
||||
if (index >= 1)
|
||||
{ // Any proxy type is used
|
||||
//enable
|
||||
m_ui->lblProxyIP->setEnabled(true);
|
||||
m_ui->textProxyIP->setEnabled(true);
|
||||
m_ui->lblProxyPort->setEnabled(true);
|
||||
m_ui->spinProxyPort->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->isProxyOnlyForTorrents->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_ui->checkProxyAuth->setEnabled(false);
|
||||
m_ui->isProxyOnlyForTorrents->setEnabled(false);
|
||||
m_ui->isProxyOnlyForTorrents->setChecked(true);
|
||||
}
|
||||
}
|
||||
else { // No proxy
|
||||
else
|
||||
{ // No proxy
|
||||
// disable
|
||||
m_ui->lblProxyIP->setEnabled(false);
|
||||
m_ui->textProxyIP->setEnabled(false);
|
||||
|
@ -1550,13 +1599,16 @@ QString OptionsDialog::getLocale() const
|
|||
void OptionsDialog::setLocale(const QString &localeStr)
|
||||
{
|
||||
QString name;
|
||||
if (localeStr.startsWith("eo", Qt::CaseInsensitive)) {
|
||||
if (localeStr.startsWith("eo", Qt::CaseInsensitive))
|
||||
{
|
||||
name = "eo";
|
||||
}
|
||||
else if (localeStr.startsWith("ltg", Qt::CaseInsensitive)) {
|
||||
else if (localeStr.startsWith("ltg", Qt::CaseInsensitive))
|
||||
{
|
||||
name = "ltg";
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QLocale locale(localeStr);
|
||||
if (locale.language() == QLocale::Uzbek)
|
||||
name = "uz@Latn";
|
||||
|
@ -1565,15 +1617,18 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
|||
}
|
||||
// Attempt to find exact match
|
||||
int index = m_ui->comboI18n->findData(name, Qt::UserRole);
|
||||
if (index < 0) {
|
||||
if (index < 0)
|
||||
{
|
||||
//Attempt to find a language match without a country
|
||||
int pos = name.indexOf('_');
|
||||
if (pos > -1) {
|
||||
if (pos > -1)
|
||||
{
|
||||
QString lang = name.left(pos);
|
||||
index = m_ui->comboI18n->findData(lang, Qt::UserRole);
|
||||
}
|
||||
}
|
||||
if (index < 0) {
|
||||
if (index < 0)
|
||||
{
|
||||
// Unrecognized, use US English
|
||||
index = m_ui->comboI18n->findData("en", Qt::UserRole);
|
||||
Q_ASSERT(index >= 0);
|
||||
|
@ -1616,10 +1671,12 @@ void OptionsDialog::on_addScanFolderButton_clicked()
|
|||
Preferences *const pref = Preferences::instance();
|
||||
const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to monitor"),
|
||||
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);
|
||||
QString error;
|
||||
switch (status) {
|
||||
switch (status)
|
||||
{
|
||||
case ScanFoldersModel::AlreadyInList:
|
||||
error = tr("Folder is already being monitored:");
|
||||
break;
|
||||
|
@ -1649,7 +1706,8 @@ void OptionsDialog::on_removeScanFolderButton_clicked()
|
|||
if (selected.isEmpty())
|
||||
return;
|
||||
Q_ASSERT(selected.count() == ScanFoldersModel::instance()->columnCount());
|
||||
for (const QModelIndex &index : selected) {
|
||||
for (const QModelIndex &index : selected)
|
||||
{
|
||||
if (index.column() == ScanFoldersModel::WATCH)
|
||||
m_removedScanDirs << index.data().toString();
|
||||
}
|
||||
|
@ -1704,13 +1762,15 @@ void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError s
|
|||
return;
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
|
||||
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)
|
||||
QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate."));
|
||||
return;
|
||||
|
@ -1728,13 +1788,15 @@ void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError sh
|
|||
return;
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
|
||||
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)
|
||||
QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key."));
|
||||
return;
|
||||
|
@ -1779,7 +1841,8 @@ void OptionsDialog::handleIPFilterParsed(bool error, int ruleCount)
|
|||
|
||||
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."));
|
||||
return false;
|
||||
}
|
||||
|
@ -1788,11 +1851,13 @@ bool OptionsDialog::schedTimesOk()
|
|||
|
||||
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."));
|
||||
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."));
|
||||
return false;
|
||||
}
|
||||
|
@ -1801,7 +1866,8 @@ bool OptionsDialog::webUIAuthenticationOk()
|
|||
|
||||
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."));
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -54,12 +54,14 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
|||
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
|
||||
drawBackground(painter, opt, index);
|
||||
|
||||
switch (index.column()) {
|
||||
switch (index.column())
|
||||
{
|
||||
case PreviewSelectDialog::SIZE:
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
break;
|
||||
|
||||
case PreviewSelectDialog::PROGRESS: {
|
||||
case PreviewSelectDialog::PROGRESS:
|
||||
{
|
||||
const qreal progress = (index.data().toReal() * 100);
|
||||
|
||||
QStyleOptionProgressBar newopt;
|
||||
|
|
|
@ -86,12 +86,14 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
|
|||
// Fill list in
|
||||
const QVector<qreal> fp = torrent->filesProgress();
|
||||
int nbFiles = torrent->filesCount();
|
||||
for (int i = 0; i < nbFiles; ++i) {
|
||||
for (int i = 0; i < nbFiles; ++i)
|
||||
{
|
||||
QString fileName = torrent->fileName(i);
|
||||
if (fileName.endsWith(QB_EXT))
|
||||
fileName.chop(4);
|
||||
QString extension = Utils::Fs::fileExtension(fileName).toUpper();
|
||||
if (Utils::Misc::isPreviewable(extension)) {
|
||||
if (Utils::Misc::isPreviewable(extension))
|
||||
{
|
||||
int row = m_previewListModel->rowCount();
|
||||
m_previewListModel->insertRow(row);
|
||||
m_previewListModel->setData(m_previewListModel->index(row, NAME), fileName);
|
||||
|
@ -128,7 +130,8 @@ void PreviewSelectDialog::previewButtonClicked()
|
|||
// Only one file should be selected
|
||||
const QString path = absolutePaths.at(selectedIndexes.at(0).data().toInt());
|
||||
// File
|
||||
if (!QFile::exists(path)) {
|
||||
if (!QFile::exists(path))
|
||||
{
|
||||
const bool isSingleFile = (m_previewListModel->rowCount() == 1);
|
||||
QWidget *parent = isSingleFile ? this->parentWidget() : this;
|
||||
QMessageBox::critical(parent, tr("Preview impossible")
|
||||
|
@ -156,7 +159,8 @@ void PreviewSelectDialog::loadWindowState()
|
|||
Utils::Gui::resize(this, m_storeDialogSize);
|
||||
|
||||
// Restore TreeView Header state
|
||||
if (!m_storeTreeHeaderState.value().isEmpty()) {
|
||||
if (!m_storeTreeHeaderState.value().isEmpty())
|
||||
{
|
||||
m_headerStateInitialized = m_ui->previewList->header()->restoreState(m_storeTreeHeaderState);
|
||||
}
|
||||
}
|
||||
|
@ -164,14 +168,16 @@ void PreviewSelectDialog::loadWindowState()
|
|||
void PreviewSelectDialog::showEvent(QShowEvent *event)
|
||||
{
|
||||
// event originated from system
|
||||
if (event->spontaneous()) {
|
||||
if (event->spontaneous())
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default size, have to be called after show(), because width is needed
|
||||
// Set Name column width to 60% of TreeView
|
||||
if (!m_headerStateInitialized) {
|
||||
if (!m_headerStateInitialized)
|
||||
{
|
||||
const int nameSize = (m_ui->previewList->size().width() * 0.6);
|
||||
m_ui->previewList->header()->resizeSection(0, nameSize);
|
||||
m_headerStateInitialized = true;
|
||||
|
|
|
@ -70,7 +70,8 @@ void ProgramUpdater::checkForUpdates()
|
|||
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;
|
||||
emit updateCheckFinished(false, QString(), m_invokedByUser);
|
||||
return;
|
||||
|
@ -81,7 +82,8 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||
#ifdef Q_OS_MACOS
|
||||
const QString OS_TYPE {"Mac OS X"};
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString OS_TYPE {(::IsWindows7OrGreater()
|
||||
const QString OS_TYPE
|
||||
{(::IsWindows7OrGreater()
|
||||
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
|
||||
? "Windows x64" : "Windows"};
|
||||
#endif
|
||||
|
@ -92,10 +94,12 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||
QString updateLink;
|
||||
QString type;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
xml.readNext();
|
||||
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.isStartElement())
|
||||
{
|
||||
if (xml.name() == "item")
|
||||
inItem = true;
|
||||
else if (inItem && xml.name() == "link")
|
||||
|
@ -105,11 +109,15 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||
else if (inItem && xml.name() == "version")
|
||||
version = getStringValue(xml);
|
||||
}
|
||||
else if (xml.isEndElement()) {
|
||||
if (inItem && xml.name() == "item") {
|
||||
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0) {
|
||||
else if (xml.isEndElement())
|
||||
{
|
||||
if (inItem && xml.name() == "item")
|
||||
{
|
||||
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
qDebug("The last update available is %s", qUtf8Printable(version));
|
||||
if (!version.isEmpty()) {
|
||||
if (!version.isEmpty())
|
||||
{
|
||||
qDebug("Detected version is %s", qUtf8Printable(version));
|
||||
if (isVersionMoreRecent(version))
|
||||
m_updateUrl = updateLink;
|
||||
|
@ -138,12 +146,14 @@ void ProgramUpdater::updateProgram()
|
|||
bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const
|
||||
{
|
||||
const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION);
|
||||
if (regVerMatch.hasMatch()) {
|
||||
if (regVerMatch.hasMatch())
|
||||
{
|
||||
const QString localVersion = regVerMatch.captured(1);
|
||||
const QVector<QStringRef> remoteParts = remoteVersion.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())
|
||||
return true;
|
||||
if (remoteParts[i].toInt() < localParts[i].toInt())
|
||||
|
|
|
@ -59,7 +59,8 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
|
|||
// image.x(0) = pieces.x(0.0 >= x < 1.7)
|
||||
// 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
|
||||
const float fromR = x * ratio;
|
||||
const float toR = (x + 1) * ratio;
|
||||
|
@ -80,15 +81,18 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
|
|||
float value = 0;
|
||||
|
||||
// case when calculated range is (15.2 >= x < 15.7)
|
||||
if (x2 == toCMinusOne) {
|
||||
if (x2 == toCMinusOne)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += ratio;
|
||||
++x2;
|
||||
}
|
||||
// case when (15.2 >= x < 17.8)
|
||||
else {
|
||||
else
|
||||
{
|
||||
// subcase (15.2 >= x < 16)
|
||||
if (x2 != fromR) {
|
||||
if (x2 != fromR)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += 1.0 - (fromR - fromC);
|
||||
++x2;
|
||||
|
@ -100,7 +104,8 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
|
|||
value += 1.0;
|
||||
|
||||
// subcase (17 >= x < 17.8)
|
||||
if (x2 == toCMinusOne) {
|
||||
if (x2 == toCMinusOne)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += 1.0 - (toC - toR);
|
||||
++x2;
|
||||
|
@ -123,12 +128,14 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
|
|||
{
|
||||
// qDebug() << "updateImage";
|
||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
||||
if (image2.isNull()) {
|
||||
if (image2.isNull())
|
||||
{
|
||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_pieces.isEmpty()) {
|
||||
if (m_pieces.isEmpty())
|
||||
{
|
||||
image2.fill(backgroundColor());
|
||||
image = image2;
|
||||
return true;
|
||||
|
@ -138,10 +145,12 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
|
|||
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
|
||||
|
||||
// filling image
|
||||
for (int x = 0; x < scaledPieces.size(); ++x) {
|
||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||
{
|
||||
float piecesToValue = scaledPieces.at(x);
|
||||
float piecesToValueDl = scaledPiecesDl.at(x);
|
||||
if (piecesToValueDl != 0) {
|
||||
if (piecesToValueDl != 0)
|
||||
{
|
||||
float fillRatio = piecesToValue + piecesToValueDl;
|
||||
float ratio = piecesToValueDl / fillRatio;
|
||||
|
||||
|
@ -150,7 +159,8 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
|
|||
|
||||
image2.setPixel(x, 0, mixedColor);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,11 @@ PeerListSortModel::PeerListSortModel(QObject *parent)
|
|||
|
||||
bool PeerListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
switch (sortColumn()) {
|
||||
switch (sortColumn())
|
||||
{
|
||||
case PeerListWidget::IP:
|
||||
case PeerListWidget::CLIENT: {
|
||||
case PeerListWidget::CLIENT:
|
||||
{
|
||||
const QString strL = left.data(UnderlyingDataRole).toString();
|
||||
const QString strR = right.data(UnderlyingDataRole).toString();
|
||||
const int result = Utils::String::naturalCompare(strL, strR, Qt::CaseInsensitive);
|
||||
|
|
|
@ -126,8 +126,10 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
|
|||
hideColumn(PeerListColumns::COUNTRY);
|
||||
// Ensure that at least one column is visible at all times
|
||||
bool atLeastOne = false;
|
||||
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) {
|
||||
if (!isColumnHidden(i)) {
|
||||
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
|
||||
{
|
||||
if (!isColumnHidden(i))
|
||||
{
|
||||
atLeastOne = true;
|
||||
break;
|
||||
}
|
||||
|
@ -137,7 +139,8 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
|
|||
// 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
|
||||
// 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))
|
||||
resizeColumnToContents(i);
|
||||
}
|
||||
|
@ -178,7 +181,8 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
|
|||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
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())
|
||||
continue;
|
||||
|
||||
|
@ -191,7 +195,8 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
|
|||
connect(menu, &QMenu::triggered, this, [this](const QAction *action)
|
||||
{
|
||||
int visibleCols = 0;
|
||||
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i) {
|
||||
for (int i = 0; i < PeerListColumns::IP_HIDDEN; ++i)
|
||||
{
|
||||
if (!isColumnHidden(i))
|
||||
++visibleCols;
|
||||
|
||||
|
@ -217,14 +222,17 @@ void PeerListWidget::displayToggleColumnsMenu(const QPoint &)
|
|||
|
||||
void PeerListWidget::updatePeerHostNameResolutionState()
|
||||
{
|
||||
if (Preferences::instance()->resolvePeerHostNames()) {
|
||||
if (!m_resolver) {
|
||||
if (Preferences::instance()->resolvePeerHostNames())
|
||||
{
|
||||
if (!m_resolver)
|
||||
{
|
||||
m_resolver = new Net::ReverseResolution(this);
|
||||
connect(m_resolver, &Net::ReverseResolution::ipResolved, this, &PeerListWidget::handleResolved);
|
||||
loadPeers(m_properties->getCurrentTorrent());
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
delete m_resolver;
|
||||
m_resolver = nullptr;
|
||||
}
|
||||
|
@ -237,13 +245,15 @@ void PeerListWidget::updatePeerCountryResolutionState()
|
|||
return;
|
||||
|
||||
m_resolveCountries = resolveCountries;
|
||||
if (m_resolveCountries) {
|
||||
if (m_resolveCountries)
|
||||
{
|
||||
loadPeers(m_properties->getCurrentTorrent());
|
||||
showColumn(PeerListColumns::COUNTRY);
|
||||
if (columnWidth(PeerListColumns::COUNTRY) <= 0)
|
||||
resizeColumnToContents(PeerListColumns::COUNTRY);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
hideColumn(PeerListColumns::COUNTRY);
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +268,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
|
|||
|
||||
// Add Peer Action
|
||||
// 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..."));
|
||||
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"));
|
||||
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers);
|
||||
|
||||
|
@ -298,7 +310,8 @@ void PeerListWidget::banSelectedPeers()
|
|||
QVector<QString> selectedIPs;
|
||||
selectedIPs.reserve(selectedIndexes.size());
|
||||
|
||||
for (const QModelIndex &index : selectedIndexes) {
|
||||
for (const QModelIndex &index : selectedIndexes)
|
||||
{
|
||||
const int row = m_proxyModel->mapToSource(index).row();
|
||||
const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->text();
|
||||
selectedIPs += ip;
|
||||
|
@ -309,7 +322,8 @@ void PeerListWidget::banSelectedPeers()
|
|||
, tr("Are you sure you want to permanently ban the selected peers?"));
|
||||
if (btn != QMessageBox::Yes) return;
|
||||
|
||||
for (const QString &ip : selectedIPs) {
|
||||
for (const QString &ip : selectedIPs)
|
||||
{
|
||||
BitTorrent::Session::instance()->banIP(ip);
|
||||
LogMsg(tr("Peer \"%1\" is manually banned").arg(ip));
|
||||
}
|
||||
|
@ -322,7 +336,8 @@ void PeerListWidget::copySelectedPeers()
|
|||
const QModelIndexList selectedIndexes = selectionModel()->selectedRows();
|
||||
QStringList selectedPeers;
|
||||
|
||||
for (const QModelIndex &index : selectedIndexes) {
|
||||
for (const QModelIndex &index : selectedIndexes)
|
||||
{
|
||||
const int row = m_proxyModel->mapToSource(index).row();
|
||||
const QString ip = m_listModel->item(row, PeerListColumns::IP_HIDDEN)->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)
|
||||
existingPeers << i.key();
|
||||
|
||||
for (const BitTorrent::PeerInfo &peer : peers) {
|
||||
for (const BitTorrent::PeerInfo &peer : peers)
|
||||
{
|
||||
if (peer.address().ip.isNull()) continue;
|
||||
|
||||
bool isNewPeer = false;
|
||||
updatePeer(torrent, peer, isNewPeer);
|
||||
if (!isNewPeer) {
|
||||
if (!isNewPeer)
|
||||
{
|
||||
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
|
||||
existingPeers.remove(peerEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove peers that are gone
|
||||
for (const PeerEndpoint &peerEndpoint : asConst(existingPeers)) {
|
||||
for (const PeerEndpoint &peerEndpoint : asConst(existingPeers))
|
||||
{
|
||||
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
||||
|
||||
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);
|
||||
isNewPeer = (itemIter == m_peerItems.end());
|
||||
if (isNewPeer) {
|
||||
if (isNewPeer)
|
||||
{
|
||||
// new item
|
||||
const int row = m_listModel->rowCount();
|
||||
m_listModel->insertRow(row);
|
||||
|
@ -449,9 +468,11 @@ void PeerListWidget::updatePeer(const BitTorrent::TorrentHandle *torrent, const
|
|||
if (m_resolver)
|
||||
m_resolver->resolve(peerEndpoint.address.ip);
|
||||
|
||||
if (m_resolveCountries) {
|
||||
if (m_resolveCountries)
|
||||
{
|
||||
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);
|
||||
const QString countryName = Net::GeoIPManager::CountryName(peer.country());
|
||||
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)
|
||||
{
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
if (event->modifiers() & Qt::ShiftModifier)
|
||||
{
|
||||
// Shift + scroll = horizontal scroll
|
||||
event->accept();
|
||||
|
||||
|
|
|
@ -57,18 +57,22 @@ QVector<BitTorrent::PeerAddress> PeersAdditionDialog::askForPeers(QWidget *paren
|
|||
|
||||
void PeersAdditionDialog::validateInput()
|
||||
{
|
||||
if (m_ui->textEditPeers->toPlainText().trimmed().isEmpty()) {
|
||||
if (m_ui->textEditPeers->toPlainText().trimmed().isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("No peer entered"),
|
||||
tr("Please type at least one peer."),
|
||||
QMessageBox::Ok);
|
||||
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);
|
||||
if (!addr.ip.isNull()) {
|
||||
if (!addr.ip.isNull())
|
||||
{
|
||||
m_peersList.append(addr);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid peer"),
|
||||
tr("The peer '%1' is invalid.").arg(peer),
|
||||
QMessageBox::Ok);
|
||||
|
|
|
@ -58,7 +58,8 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
|||
// image.x(0) = pieces.x(0.0 >= x < 1.7)
|
||||
// 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
|
||||
const float fromR = x * ratio;
|
||||
const float toR = (x + 1) * ratio;
|
||||
|
@ -79,15 +80,18 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
|||
float value = 0;
|
||||
|
||||
// case when calculated range is (15.2 >= x < 15.7)
|
||||
if (x2 == toCMinusOne) {
|
||||
if (x2 == toCMinusOne)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += ratio * vecin[x2];
|
||||
++x2;
|
||||
}
|
||||
// case when (15.2 >= x < 17.8)
|
||||
else {
|
||||
else
|
||||
{
|
||||
// subcase (15.2 >= x < 16)
|
||||
if (x2 != fromR) {
|
||||
if (x2 != fromR)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += (1.0 - (fromR - fromC)) * vecin[x2];
|
||||
++x2;
|
||||
|
@ -99,7 +103,8 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
|||
value += vecin[x2];
|
||||
|
||||
// subcase (17 >= x < 17.8)
|
||||
if (x2 == toCMinusOne) {
|
||||
if (x2 == toCMinusOne)
|
||||
{
|
||||
if (vecin[x2])
|
||||
value += (1.0 - (toC - toR)) * vecin[x2];
|
||||
++x2;
|
||||
|
@ -121,12 +126,14 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
|||
bool PieceAvailabilityBar::updateImage(QImage &image)
|
||||
{
|
||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
||||
if (image2.isNull()) {
|
||||
if (image2.isNull())
|
||||
{
|
||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_pieces.empty()) {
|
||||
if (m_pieces.empty())
|
||||
{
|
||||
image2.fill(backgroundColor());
|
||||
image = image2;
|
||||
return true;
|
||||
|
@ -135,7 +142,8 @@ bool PieceAvailabilityBar::updateImage(QImage &image)
|
|||
QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width());
|
||||
|
||||
// filling image
|
||||
for (int x = 0; x < scaledPieces.size(); ++x) {
|
||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||
{
|
||||
float piecesToValue = scaledPieces.at(x);
|
||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ namespace
|
|||
{
|
||||
public:
|
||||
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}
|
||||
, m_torrentInfo {torrentInfo}
|
||||
{
|
||||
|
@ -133,7 +134,8 @@ void PiecesBar::clear()
|
|||
|
||||
bool PiecesBar::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::ToolTip) {
|
||||
if (e->type() == QEvent::ToolTip)
|
||||
{
|
||||
showToolTip(static_cast<QHelpEvent *>(e));
|
||||
return true;
|
||||
}
|
||||
|
@ -167,17 +169,20 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
|||
{
|
||||
QPainter painter(this);
|
||||
QRect imageRect(borderWidth, borderWidth, width() - 2 * borderWidth, height() - 2 * borderWidth);
|
||||
if (m_image.isNull()) {
|
||||
if (m_image.isNull())
|
||||
{
|
||||
painter.setBrush(backgroundColor());
|
||||
painter.drawRect(imageRect);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (m_image.width() != imageRect.width())
|
||||
updateImage(m_image);
|
||||
painter.drawImage(imageRect, m_image);
|
||||
}
|
||||
|
||||
if (!m_highlitedRegion.isNull()) {
|
||||
if (!m_highlitedRegion.isNull())
|
||||
{
|
||||
QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)};
|
||||
highlightColor.setAlphaF(0.35);
|
||||
QRect targetHighlightRect {m_highlitedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
||||
|
@ -247,19 +252,23 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
|
|||
QString toolTipText;
|
||||
QTextStream stream(&toolTipText, QIODevice::WriteOnly);
|
||||
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;
|
||||
if ((imagePos >=0) && (imagePos < m_image.width())) {
|
||||
if ((imagePos >=0) && (imagePos < m_image.width()))
|
||||
{
|
||||
stream << "<html><body>";
|
||||
PieceIndexToImagePos transform {m_torrent->info(), m_image};
|
||||
int pieceIndex = transform.pieceIndex(imagePos);
|
||||
const QVector<int> files {m_torrent->info().fileIndicesForPiece(pieceIndex)};
|
||||
|
||||
QString tooltipTitle;
|
||||
if (files.count() > 1) {
|
||||
if (files.count() > 1)
|
||||
{
|
||||
tooltipTitle = tr("Files in this piece:");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (m_torrent->info().fileSize(files.front()) == m_torrent->info().pieceLength(pieceIndex))
|
||||
tooltipTitle = tr("File in this piece");
|
||||
else
|
||||
|
@ -268,14 +277,16 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
|
|||
|
||||
DetailedTooltipRenderer renderer(stream, tooltipTitle);
|
||||
|
||||
for (int f : files) {
|
||||
for (int f : files)
|
||||
{
|
||||
const QString filePath {m_torrent->info().filePath(f)};
|
||||
renderer(Utils::Misc::friendlyUnit(m_torrent->info().fileSize(f)), filePath);
|
||||
}
|
||||
stream << "</body></html>";
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
stream << simpleToolTipText();
|
||||
if (showDetailedInformation) // metadata are not available at this point
|
||||
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);
|
||||
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());
|
||||
|
||||
ImageRange imageRange = transform.imagePos(filePieces);
|
||||
QRect newHighlitedRegion {imageRange.first(), 0, imageRange.size(), m_image.height()};
|
||||
if (newHighlitedRegion != m_highlitedRegion) {
|
||||
if (newHighlitedRegion != m_highlitedRegion)
|
||||
{
|
||||
m_highlitedRegion = newHighlitedRegion;
|
||||
update();
|
||||
}
|
||||
}
|
||||
else if (!m_highlitedRegion.isEmpty()) {
|
||||
else if (!m_highlitedRegion.isEmpty())
|
||||
{
|
||||
m_highlitedRegion = QRect();
|
||||
update();
|
||||
}
|
||||
|
@ -316,7 +330,8 @@ void PiecesBar::highlightFile(int imagePos)
|
|||
void PiecesBar::updatePieceColors()
|
||||
{
|
||||
m_pieceColors = QVector<QRgb>(256);
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
float ratio = (i / 255.0);
|
||||
m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio);
|
||||
}
|
||||
|
|
|
@ -194,7 +194,8 @@ void PropertiesWidget::showPiecesDownloaded(bool show)
|
|||
|
||||
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
|
||||
auto *hSplitter = static_cast<QSplitter *>(parentWidget());
|
||||
m_ui->stackedProperties->setVisible(false);
|
||||
|
@ -210,7 +211,8 @@ void PropertiesWidget::setVisibility(const bool visible)
|
|||
return;
|
||||
}
|
||||
|
||||
if (visible && (m_state == REDUCED)) {
|
||||
if (visible && (m_state == REDUCED))
|
||||
{
|
||||
m_ui->stackedProperties->setVisible(true);
|
||||
auto *hSplitter = static_cast<QSplitter *>(parentWidget());
|
||||
if (m_handleWidth != -1)
|
||||
|
@ -314,7 +316,8 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent
|
|||
// Hash
|
||||
m_ui->labelHashVal->setText(m_torrent->hash());
|
||||
m_propListModel->model()->clear();
|
||||
if (m_torrent->hasMetadata()) {
|
||||
if (m_torrent->hasMetadata())
|
||||
{
|
||||
// Creation date
|
||||
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
|
||||
QModelIndex currentIndex;
|
||||
while (m_propListModel->rowCount(currentIndex) == 1) {
|
||||
while (m_propListModel->rowCount(currentIndex) == 1)
|
||||
{
|
||||
currentIndex = m_propListModel->index(0, 0, currentIndex);
|
||||
m_ui->filesList->setExpanded(currentIndex, true);
|
||||
}
|
||||
|
@ -350,7 +354,8 @@ void PropertiesWidget::readSettings()
|
|||
const Preferences *const pref = Preferences::instance();
|
||||
// Restore splitter sizes
|
||||
QStringList sizesStr = pref->getPropSplitterSizes().split(',');
|
||||
if (sizesStr.size() == 2) {
|
||||
if (sizesStr.size() == 2)
|
||||
{
|
||||
m_slideSizes << sizesStr.first().toInt();
|
||||
m_slideSizes << sizesStr.last().toInt();
|
||||
auto *hSplitter = static_cast<QSplitter *>(parentWidget());
|
||||
|
@ -396,8 +401,10 @@ void PropertiesWidget::loadDynamicData()
|
|||
if (!m_torrent || (m_state != VISIBLE)) return;
|
||||
|
||||
// Transfer infos
|
||||
switch (m_ui->stackedProperties->currentIndex()) {
|
||||
case PropTabBar::MainTab: {
|
||||
switch (m_ui->stackedProperties->currentIndex())
|
||||
{
|
||||
case PropTabBar::MainTab:
|
||||
{
|
||||
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())
|
||||
|
@ -456,16 +463,19 @@ void PropertiesWidget::loadDynamicData()
|
|||
|
||||
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()));
|
||||
|
||||
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
|
||||
showPiecesAvailability(true);
|
||||
m_piecesAvailability->setAvailability(m_torrent->pieceAvailability());
|
||||
m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3));
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
showPiecesAvailability(false);
|
||||
}
|
||||
|
||||
|
@ -474,7 +484,8 @@ void PropertiesWidget::loadDynamicData()
|
|||
m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + '%');
|
||||
m_downloadedPieces->setProgress(m_torrent->pieces(), m_torrent->downloadingPieces());
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
showPiecesAvailability(false);
|
||||
}
|
||||
}
|
||||
|
@ -489,7 +500,8 @@ void PropertiesWidget::loadDynamicData()
|
|||
break;
|
||||
case PropTabBar::FilesTab:
|
||||
// Files progress
|
||||
if (m_torrent->hasMetadata()) {
|
||||
if (m_torrent->hasMetadata())
|
||||
{
|
||||
qDebug("Updating priorities in files tab");
|
||||
m_ui->filesList->setUpdatesEnabled(false);
|
||||
m_propListModel->model()->updateFilesProgress(m_torrent->filesProgress());
|
||||
|
@ -511,7 +523,8 @@ void PropertiesWidget::loadUrlSeeds()
|
|||
qDebug("Loading URL seeds");
|
||||
const QVector<QUrl> hcSeeds = m_torrent->urlSeeds();
|
||||
// Add url seeds
|
||||
for (const QUrl &hcSeed : hcSeeds) {
|
||||
for (const QUrl &hcSeed : hcSeeds)
|
||||
{
|
||||
qDebug("Loading URL seed: %s", qUtf8Printable(hcSeed.toString()));
|
||||
new QListWidgetItem(hcSeed.toString(), m_ui->listWebSeeds);
|
||||
}
|
||||
|
@ -519,7 +532,8 @@ void PropertiesWidget::loadUrlSeeds()
|
|||
|
||||
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 QString filename {m_torrent->filePath(fileIdx)};
|
||||
const QDir saveDir {m_torrent->savePath(true)};
|
||||
|
@ -568,7 +582,8 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
|
|||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (selectedRows.size() == 1) {
|
||||
if (selectedRows.size() == 1)
|
||||
{
|
||||
const QModelIndex index = selectedRows[0];
|
||||
|
||||
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open"));
|
||||
|
@ -583,12 +598,14 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
|
|||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (!m_torrent->isSeed()) {
|
||||
if (!m_torrent->isSeed())
|
||||
{
|
||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||
|
||||
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
|
||||
{
|
||||
for (const QModelIndex &index : selectedRows) {
|
||||
for (const QModelIndex &index : selectedRows)
|
||||
{
|
||||
m_propListModel->setData(
|
||||
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"));
|
||||
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"));
|
||||
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds);
|
||||
|
||||
|
@ -673,16 +691,20 @@ void PropertiesWidget::openSelectedFile()
|
|||
void PropertiesWidget::configure()
|
||||
{
|
||||
// Speed widget
|
||||
if (Preferences::instance()->isSpeedWidgetEnabled()) {
|
||||
if (!m_speedWidget || !qobject_cast<SpeedWidget *>(m_speedWidget)) {
|
||||
if (Preferences::instance()->isSpeedWidgetEnabled())
|
||||
{
|
||||
if (!m_speedWidget || !qobject_cast<SpeedWidget *>(m_speedWidget))
|
||||
{
|
||||
m_ui->speedLayout->removeWidget(m_speedWidget);
|
||||
delete m_speedWidget;
|
||||
m_speedWidget = new SpeedWidget {this};
|
||||
m_ui->speedLayout->addWidget(m_speedWidget);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!m_speedWidget || !qobject_cast<QLabel *>(m_speedWidget)) {
|
||||
else
|
||||
{
|
||||
if (!m_speedWidget || !qobject_cast<QLabel *>(m_speedWidget))
|
||||
{
|
||||
m_ui->speedLayout->removeWidget(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);
|
||||
|
@ -702,7 +724,8 @@ void PropertiesWidget::askWebSeed()
|
|||
QLatin1String("http://www."), &ok);
|
||||
if (!ok) return;
|
||||
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",
|
||||
tr("This URL seed is already in the list."),
|
||||
QMessageBox::Ok);
|
||||
|
@ -755,7 +778,8 @@ void PropertiesWidget::editWebSeed()
|
|||
oldSeed, &result);
|
||||
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"),
|
||||
tr("This URL seed is already in the list."),
|
||||
QMessageBox::Ok);
|
||||
|
@ -781,11 +805,13 @@ void PropertiesWidget::filteredFilesChanged()
|
|||
void PropertiesWidget::filterText(const QString &filter)
|
||||
{
|
||||
m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix));
|
||||
if (filter.isEmpty()) {
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
m_ui->filesList->collapseAll();
|
||||
m_ui->filesList->expand(m_propListModel->index(0, 0));
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
m_ui->filesList->expandAll();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,11 +72,13 @@ void PropListDelegate::initProgressStyleOption(QStyleOptionProgressBar &option,
|
|||
ProgressBarDelegate::initProgressStyleOption(option, index);
|
||||
const int priority
|
||||
= 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.palette = progressBarDisabledPalette();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
option.state |= QStyle::State_Enabled;
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +88,8 @@ void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
|
|||
auto *combobox = static_cast<QComboBox *>(editor);
|
||||
// Set combobox index
|
||||
const int priority = index.data(TorrentContentModel::UnderlyingDataRole).toInt();
|
||||
switch (static_cast<BitTorrent::DownloadPriority>(priority)) {
|
||||
switch (static_cast<BitTorrent::DownloadPriority>(priority))
|
||||
{
|
||||
case BitTorrent::DownloadPriority::Ignored:
|
||||
combobox->setCurrentIndex(0);
|
||||
break;
|
||||
|
@ -106,7 +109,8 @@ QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
|
|||
{
|
||||
if (index.column() != PRIORITY) return nullptr;
|
||||
|
||||
if (m_properties) {
|
||||
if (m_properties)
|
||||
{
|
||||
const BitTorrent::TorrentHandle *torrent = m_properties->getCurrentTorrent();
|
||||
if (!torrent || !torrent->hasMetadata() || torrent->isSeed())
|
||||
return nullptr;
|
||||
|
@ -131,7 +135,8 @@ void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
|||
const int value = combobox->currentIndex();
|
||||
|
||||
BitTorrent::DownloadPriority prio = BitTorrent::DownloadPriority::Normal; // NORMAL
|
||||
switch (value) {
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
prio = BitTorrent::DownloadPriority::Ignored; // IGNORED
|
||||
break;
|
||||
|
|
|
@ -122,8 +122,10 @@ void PropTabBar::setCurrentIndex(int index)
|
|||
if (index >= m_btnGroup->buttons().size())
|
||||
index = 0;
|
||||
// If asked to hide or if the currently selected tab is clicked
|
||||
if ((index < 0) || (m_currentIndex == index)) {
|
||||
if (m_currentIndex >= 0) {
|
||||
if ((index < 0) || (m_currentIndex == index))
|
||||
{
|
||||
if (m_currentIndex >= 0)
|
||||
{
|
||||
m_btnGroup->button(m_currentIndex)->setDown(false);
|
||||
m_currentIndex = -1;
|
||||
emit visibilityToggled(false);
|
||||
|
@ -131,10 +133,12 @@ void PropTabBar::setCurrentIndex(int index)
|
|||
return;
|
||||
}
|
||||
// Unselect previous tab
|
||||
if (m_currentIndex >= 0) {
|
||||
if (m_currentIndex >= 0)
|
||||
{
|
||||
m_btnGroup->button(m_currentIndex)->setDown(false);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Nothing was selected, show!
|
||||
emit visibilityToggled(true);
|
||||
}
|
||||
|
|
|
@ -79,26 +79,30 @@ namespace
|
|||
if (value <= 12.0) return {12, SizeUnit::Byte};
|
||||
|
||||
SizeUnit calculatedUnit = SizeUnit::Byte;
|
||||
while (value > 1024) {
|
||||
while (value > 1024)
|
||||
{
|
||||
value /= 1024;
|
||||
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;
|
||||
while (roundedValue < value)
|
||||
roundedValue += 40;
|
||||
return {static_cast<double>(roundedValue), calculatedUnit};
|
||||
}
|
||||
|
||||
if (value > 10.0) {
|
||||
if (value > 10.0)
|
||||
{
|
||||
int roundedValue = static_cast<int>(value / 4) * 4;
|
||||
while (roundedValue < value)
|
||||
roundedValue += 4;
|
||||
return {static_cast<double>(roundedValue), calculatedUnit};
|
||||
}
|
||||
|
||||
for (const auto &roundedValue : roundingTable) {
|
||||
for (const auto &roundedValue : roundingTable)
|
||||
{
|
||||
if (value <= roundedValue)
|
||||
return {roundedValue, calculatedUnit};
|
||||
}
|
||||
|
@ -215,7 +219,8 @@ void SpeedPlotView::setPeriod(const TimePeriod period)
|
|||
{
|
||||
m_period = period;
|
||||
|
||||
switch (period) {
|
||||
switch (period)
|
||||
{
|
||||
case SpeedPlotView::MIN1:
|
||||
m_viewablePointsCount = MIN1_SEC;
|
||||
m_currentData = &m_data5Min;
|
||||
|
@ -266,7 +271,8 @@ quint64 SpeedPlotView::maxYValue()
|
|||
boost::circular_buffer<PointData> &queue = getCurrentData();
|
||||
|
||||
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)
|
||||
continue;
|
||||
|
@ -292,7 +298,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||
rect.adjust(0, fontMetrics.height(), 0, 0); // Add top padding for top speed text
|
||||
|
||||
// draw Y axis speed labels
|
||||
const QVector<QString> speedLabels = {
|
||||
const QVector<QString> speedLabels =
|
||||
{
|
||||
formatLabel(niceScale.arg, niceScale.unit),
|
||||
formatLabel((0.75 * niceScale.arg), niceScale.unit),
|
||||
formatLabel((0.50 * niceScale.arg), niceScale.unit),
|
||||
|
@ -311,7 +318,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||
#endif
|
||||
|
||||
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()),
|
||||
QSizeF(2 * yAxisWidth, fontMetrics.height()));
|
||||
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());
|
||||
|
||||
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;
|
||||
painter.drawLine(x, fullRect.top(), x, fullRect.bottom());
|
||||
}
|
||||
|
@ -349,12 +358,14 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||
|
||||
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)
|
||||
continue;
|
||||
|
||||
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 newY = rect.bottom() - queue[i].y[id] * yMultiplier;
|
||||
|
@ -371,7 +382,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||
|
||||
double legendHeight = 0;
|
||||
int legendWidth = 0;
|
||||
for (const auto &property : asConst(m_properties)) {
|
||||
for (const auto &property : asConst(m_properties))
|
||||
{
|
||||
if (!property.enable)
|
||||
continue;
|
||||
|
||||
|
@ -391,7 +403,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||
painter.fillRect(legendBackgroundRect, legendBackgroundColor);
|
||||
|
||||
i = 0;
|
||||
for (const auto &property : asConst(m_properties)) {
|
||||
for (const auto &property : asConst(m_properties))
|
||||
{
|
||||
if (!property.enable)
|
||||
continue;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue