mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-29 19:58:06 -06:00
Compare commits
269 Commits
release-4.
...
v4_0_x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f3d1ec2c | ||
|
|
bc6e3ae40d | ||
|
|
b02e239f7c | ||
|
|
397cd4bf60 | ||
|
|
409f972ad3 | ||
|
|
267961ffca | ||
|
|
dff753c452 | ||
|
|
ce3bafd30d | ||
|
|
e5538d9f25 | ||
|
|
7a8a32b1c3 | ||
|
|
cac5e0391b | ||
|
|
726790fa93 | ||
|
|
c9c7a5be53 | ||
|
|
1495513cfc | ||
|
|
085afaac14 | ||
|
|
d58a54c758 | ||
|
|
78d9bcb6a1 | ||
|
|
1a43cd329d | ||
|
|
2fe687eeca | ||
|
|
107bd8a54f | ||
|
|
865df3fcf1 | ||
|
|
cbf9c52462 | ||
|
|
171e25e962 | ||
|
|
239d14fd10 | ||
|
|
0b7a175156 | ||
|
|
b37dbb60b5 | ||
|
|
d5acd1f210 | ||
|
|
65eda4a68e | ||
|
|
7209881025 | ||
|
|
a5d0a4b619 | ||
|
|
79276a8786 | ||
|
|
fa2b645a64 | ||
|
|
4d5d6df734 | ||
|
|
2c39b69c18 | ||
|
|
13c0077e95 | ||
|
|
9299e3f371 | ||
|
|
b9ddc6ee86 | ||
|
|
276856a614 | ||
|
|
fbd6a8a0da | ||
|
|
6fc18b4af6 | ||
|
|
44d633fb68 | ||
|
|
eb4bf6cc68 | ||
|
|
6db6c850eb | ||
|
|
02ae1e3734 | ||
|
|
eb887139fd | ||
|
|
84805f7fb8 | ||
|
|
2719131ed2 | ||
|
|
52401bd2b0 | ||
|
|
4834703bc4 | ||
|
|
3ed73244b1 | ||
|
|
97cd430125 | ||
|
|
d202b85d51 | ||
|
|
c51b79e9fc | ||
|
|
4449018207 | ||
|
|
ced8e41473 | ||
|
|
2c66ed6708 | ||
|
|
c7d3d6ac90 | ||
|
|
13210b3e9f | ||
|
|
6e622fc23b | ||
|
|
ae35111b59 | ||
|
|
e1c3d419a7 | ||
|
|
7396b8adba | ||
|
|
c09001545d | ||
|
|
f8d4315f7e | ||
|
|
1fa2957d27 | ||
|
|
ade50d2b53 | ||
|
|
0fa1d35b87 | ||
|
|
6486fc5f4d | ||
|
|
1e059ab1a2 | ||
|
|
15b137211b | ||
|
|
6f8f1d7bad | ||
|
|
a31f0c0a3d | ||
|
|
f977d1293a | ||
|
|
1399be50cb | ||
|
|
52dcf32cc8 | ||
|
|
52b2b807ab | ||
|
|
5cf4f00824 | ||
|
|
faa6fad025 | ||
|
|
9f94bbce3a | ||
|
|
5c49b2486c | ||
|
|
4f6e7f97c6 | ||
|
|
7751c5b75c | ||
|
|
a1a9f3317b | ||
|
|
fb20f59a96 | ||
|
|
a15e3407b0 | ||
|
|
e267c2d37a | ||
|
|
ae32edeb26 | ||
|
|
34d38ef466 | ||
|
|
120ee6b836 | ||
|
|
7d25b6fce2 | ||
|
|
068eff9e9f | ||
|
|
31a55f79f1 | ||
|
|
bac032e01c | ||
|
|
b809941f02 | ||
|
|
77c3758090 | ||
|
|
5758817189 | ||
|
|
acc9f08a05 | ||
|
|
f3b7f17a7c | ||
|
|
dfc3f047e2 | ||
|
|
223ab7de84 | ||
|
|
d2a4027347 | ||
|
|
4594895082 | ||
|
|
e457223fcd | ||
|
|
8fc25c4524 | ||
|
|
410e133592 | ||
|
|
e114bc7ef6 | ||
|
|
6ac57cb24c | ||
|
|
2b7893adc8 | ||
|
|
84b8832d57 | ||
|
|
0e738b534c | ||
|
|
96ce8690b6 | ||
|
|
a23698940c | ||
|
|
50bb733293 | ||
|
|
6420157b55 | ||
|
|
86bdfbf88c | ||
|
|
81e8f79164 | ||
|
|
64a0ad33c1 | ||
|
|
3cd0ffecaf | ||
|
|
a2ddabaedb | ||
|
|
1fec1978aa | ||
|
|
8de67fd745 | ||
|
|
3b51582416 | ||
|
|
ffa2fdce9d | ||
|
|
588f1c7592 | ||
|
|
ab1ece2460 | ||
|
|
7a935d8a87 | ||
|
|
3926eba585 | ||
|
|
74bf420610 | ||
|
|
324f18a0b2 | ||
|
|
c134e391e6 | ||
|
|
24504951b0 | ||
|
|
f7f02ab16a | ||
|
|
fe810fcd37 | ||
|
|
17167e79d2 | ||
|
|
7bd86048a8 | ||
|
|
d399f024a7 | ||
|
|
21f06abef8 | ||
|
|
fbe0e96fd5 | ||
|
|
94e00dd38d | ||
|
|
c3f5432877 | ||
|
|
4dcc187a72 | ||
|
|
97c99dfaaf | ||
|
|
da83041a3f | ||
|
|
d40a4f14dd | ||
|
|
de7b0278f4 | ||
|
|
17f5e10ffc | ||
|
|
a0dbb6c97c | ||
|
|
4d330a6110 | ||
|
|
9fc2bf6353 | ||
|
|
f9c7121847 | ||
|
|
d3a0ac3b6e | ||
|
|
a6c99844de | ||
|
|
d51a957247 | ||
|
|
a0c16cd461 | ||
|
|
8fe11dff91 | ||
|
|
efcdcf5898 | ||
|
|
da543cdae2 | ||
|
|
0374742e57 | ||
|
|
408052d1ec | ||
|
|
b0ebbc3596 | ||
|
|
e45e1166b2 | ||
|
|
de64d5c3bc | ||
|
|
07130c4b26 | ||
|
|
8482464ad0 | ||
|
|
d7ce6e39d4 | ||
|
|
97acbd5259 | ||
|
|
60937a1871 | ||
|
|
ed43bc377d | ||
|
|
8d11929815 | ||
|
|
0e6f8c15c5 | ||
|
|
8107201a5b | ||
|
|
395ea4d1d0 | ||
|
|
7bf317929b | ||
|
|
3cacf876c9 | ||
|
|
d6247dd4ec | ||
|
|
4f0c49f1c4 | ||
|
|
30455e8b01 | ||
|
|
60adb94463 | ||
|
|
a02fd5b588 | ||
|
|
39ce080318 | ||
|
|
f53abd2f07 | ||
|
|
5b0ae0271b | ||
|
|
ec2efd8c62 | ||
|
|
146daea513 | ||
|
|
5ab67faacb | ||
|
|
4213d37857 | ||
|
|
0192922910 | ||
|
|
d2b88e9f84 | ||
|
|
a32c4aca92 | ||
|
|
91d41336a7 | ||
|
|
d73d790612 | ||
|
|
af0fed6669 | ||
|
|
a24c13b902 | ||
|
|
c44c6a8d88 | ||
|
|
5f62a68e71 | ||
|
|
5af90fee46 | ||
|
|
b17566f113 | ||
|
|
29edea050b | ||
|
|
7ceb646e90 | ||
|
|
0ff39e4d10 | ||
|
|
1e146c94bd | ||
|
|
8a0da04807 | ||
|
|
9e7a847cce | ||
|
|
63d3f20e51 | ||
|
|
817e3fbb05 | ||
|
|
263e96aba2 | ||
|
|
0379376fd8 | ||
|
|
de7efb50c2 | ||
|
|
400f8dc2d8 | ||
|
|
b2b63be798 | ||
|
|
c9aba893de | ||
|
|
1ac4cdcf4d | ||
|
|
08a0fef18a | ||
|
|
6f54c170ab | ||
|
|
d3b4c7bec4 | ||
|
|
f8dfe1ea57 | ||
|
|
ce5f8bab44 | ||
|
|
59cf70f8f2 | ||
|
|
561975f435 | ||
|
|
eae6fea830 | ||
|
|
2673c2b5b2 | ||
|
|
3c17f3a836 | ||
|
|
0890154e16 | ||
|
|
0877824875 | ||
|
|
60bd5999b0 | ||
|
|
d0ec60fa01 | ||
|
|
e7a70a4acc | ||
|
|
85cb49e8e1 | ||
|
|
3f00a6e5e3 | ||
|
|
35e18a2e09 | ||
|
|
8ae2ae3b5c | ||
|
|
27c5f2aede | ||
|
|
34a69aa0b2 | ||
|
|
72fc903f4a | ||
|
|
4f04992de8 | ||
|
|
1b147494d4 | ||
|
|
b535a0b44e | ||
|
|
6c2271584c | ||
|
|
1002b28c95 | ||
|
|
dfded7bc9d | ||
|
|
36fde9ede5 | ||
|
|
6b4ac1b960 | ||
|
|
cc141ba02f | ||
|
|
8fc931a61b | ||
|
|
0b6cf54508 | ||
|
|
ff12163176 | ||
|
|
6a8a0bbd6b | ||
|
|
f8ebffac65 | ||
|
|
e58f4c0bdf | ||
|
|
f450ff278d | ||
|
|
566fd893f4 | ||
|
|
30ab46999c | ||
|
|
0320f9d5b5 | ||
|
|
ad7c9ed123 | ||
|
|
25acdba344 | ||
|
|
786059802b | ||
|
|
0ae708114b | ||
|
|
fbeaabb841 | ||
|
|
98bef605a7 | ||
|
|
37a0e48b46 | ||
|
|
fea1a66aba | ||
|
|
0ffdb51f95 | ||
|
|
4e596629fd | ||
|
|
61281dd226 | ||
|
|
61d1f2180f | ||
|
|
3bcf941205 | ||
|
|
1746c9d331 | ||
|
|
58c31c5353 | ||
|
|
e872719ef1 |
@@ -134,13 +134,13 @@ install:
|
||||
cp "version" $HOME/hombebrew_cache
|
||||
cd "$HOME/hombebrew_cache"
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.1.6+git20180101.b45acf28a5+patched-configure.el_capitan.bottle.tar.gz
|
||||
fi
|
||||
|
||||
# Copy custom libtorrent bottle to homebrew's cache so it can find and install it
|
||||
# Also install our custom libtorrent formula by passing the local path to it
|
||||
# These 2 files are restored from Travis' cache.
|
||||
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)"
|
||||
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.1.6+git20180101.b45acf28a5+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)"
|
||||
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_policy(VERSION 3.5)
|
||||
|
||||
message(WARNING "No official support for cmake build system. If it is broken, please submit patches!")
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
|
||||
include(FunctionReadVersion)
|
||||
|
||||
@@ -45,13 +43,13 @@ option(GUI "Allows to disable GUI for headless running. Disables QtDBus and the
|
||||
|
||||
option(WEBUI "Allows to disable the WebUI." ON)
|
||||
|
||||
option(STACKTRACE "Enable stacktrace feature" ON)
|
||||
|
||||
if (UNIX)
|
||||
if (WIN32)
|
||||
option(STACKTRACE_WIN "")
|
||||
else (WIN32)
|
||||
cmake_dependent_option(SYSTEMD "Install the systemd service file (headless only)" OFF
|
||||
"NOT GUI" OFF)
|
||||
cmake_dependent_option(DBUS "Enable use of QtDBus (GUI only)" ON "GUI" OFF)
|
||||
endif(UNIX)
|
||||
endif(WIN32)
|
||||
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
All new code **must** follow the following coding guidelines.
|
||||
If you make changes in a file that still uses another coding style, make sure that you follow these guidelines for your changes.
|
||||
For programming languages other than C++ (e.g. JavaScript) used in this repository and submodules, unless otherwise specified, coding guidelines listed here applies as much as possible.
|
||||
|
||||
All new code must follow the following coding guidelines.
|
||||
If you make changes in a file that still uses another coding style, make sure that you follow these guidelines for your changes instead.
|
||||
**Note 1:** I will not take your head if you forget and use another style. However, most probably the request will be delayed until you fix your coding style.
|
||||
**Note 2:** You can use the `uncrustify` program/tool to clean up any source file. Use it with the `uncrustify.cfg` configuration file found in the root folder.
|
||||
**Note 3:** There is also a style for QtCreator but it doesn't cover all cases. In QtCreator `Tools->Options...->C++->Code Style->Import...` and choose the `codingStyleQtCreator.xml` file found in the root folder.
|
||||
|
||||
### 1. New lines & curly braces ###
|
||||
|
||||
### 1. Curly braces ###
|
||||
#### a. Function blocks, class/struct definitions, namespaces ####
|
||||
```c++
|
||||
int myFunction(int a)
|
||||
@@ -92,8 +89,18 @@ default:
|
||||
}
|
||||
```
|
||||
|
||||
#### d. If-else statements ####
|
||||
The `else if`/`else` must be on their own lines:
|
||||
#### d. Brace enclosed initializers ####
|
||||
Unlike single-line functions, you must not insert spaces between the brackets and concluded expressions.<br/>
|
||||
But you must insert a space between the variable name and initializer.
|
||||
```c++
|
||||
Class obj {}; // empty
|
||||
Class obj {expr};
|
||||
Class obj {expr1, /*...,*/ exprN};
|
||||
QVariantMap map {{"key1", 5}, {"key2", 10}};
|
||||
```
|
||||
|
||||
### 2. If blocks ###
|
||||
#### a. Multiple tests ####
|
||||
```c++
|
||||
if (condition) {
|
||||
// code
|
||||
@@ -105,71 +112,40 @@ else {
|
||||
// code
|
||||
}
|
||||
```
|
||||
The `else if`/`else` must be on their own lines.
|
||||
|
||||
#### e. Single statement if blocks ####
|
||||
Most single statement if blocks should look like this:
|
||||
#### b. Single statement if blocks ####
|
||||
**Most** single statement if blocks should look like this:
|
||||
```c++
|
||||
if (condition)
|
||||
a = a + b;
|
||||
```
|
||||
|
||||
One acceptable exception to this can be `return`, `break` or `continue` statements,
|
||||
provided that the test condition isn't very long and its body statement occupies only one line.
|
||||
However you can still choose to use the first rule.
|
||||
One acceptable exception to this **can be** `return`, `break` or `continue` statements, provided that the test condition isn't very long. However you can choose to use the first rule instead.
|
||||
```c++
|
||||
if (a > 0) return;
|
||||
a = myFunction();
|
||||
b = a * 1500;
|
||||
|
||||
while (p) {
|
||||
// ...
|
||||
if (!b) continue;
|
||||
}
|
||||
if (b > 0) return;
|
||||
c = 100 / b;
|
||||
```
|
||||
|
||||
#### f. Acceptable conditions to omit braces ####
|
||||
When the conditional statement in `if`/`else` has only one line and its body occupy only one line,
|
||||
this also applies to loops statements.
|
||||
Notice that for a series of `if - else` branches, if one branch needs braces then all branches must add braces.
|
||||
```c++
|
||||
if (a < b) // conditional statement
|
||||
do(a); // body
|
||||
#### c. Using curly braces for single statement if blocks ####
|
||||
|
||||
if (a < b)
|
||||
do(a);
|
||||
else if (a > b)
|
||||
do(b);
|
||||
else
|
||||
do(c);
|
||||
However, there are cases where curly braces for single statement if blocks **should** be used.
|
||||
* If some branch needs braces then all others should use them. Unless you have multiple `else if` in a row and the one needing the braces is only for a very small sub-block of code.
|
||||
* Another exception would be when we have nested if blocks or generally multiple levels of code that affect code readability.
|
||||
|
||||
if (a < b) {
|
||||
do(a);
|
||||
}
|
||||
else if (a > b) { // curly braces required here, then all branches should also add them
|
||||
do(b);
|
||||
do(d);
|
||||
}
|
||||
else {
|
||||
do(c);
|
||||
}
|
||||
```
|
||||
Generally it will depend on the particular piece of code and would be determined on how readable that piece of code is. **If in doubt** always use braces if one of the above exceptions applies.
|
||||
|
||||
#### g. Brace enclosed initializers ####
|
||||
Unlike single-line functions, you must not insert spaces between the brackets and concluded expressions.<br/>
|
||||
But you must insert a space between the variable name and initializer.
|
||||
```c++
|
||||
Class obj {}; // empty
|
||||
Class obj {expr};
|
||||
Class obj {expr1, /*...,*/ exprN};
|
||||
QVariantMap map {{"key1", 5}, {"key2", 10}};
|
||||
```
|
||||
|
||||
### 2. Indentation ###
|
||||
### 3. Indentation ###
|
||||
4 spaces.
|
||||
|
||||
### 3. File encoding and line endings. ###
|
||||
### 4. File encoding and line endings. ###
|
||||
|
||||
UTF-8 and Unix-like line ending (LF). Unless some platform specific files need other encodings/line endings.
|
||||
|
||||
### 4. Initialization lists. ###
|
||||
### 5. Initialization lists. ###
|
||||
Initialization lists should be vertical. This will allow for more easily readable diffs. The initialization colon should be indented and in its own line along with first argument. The rest of the arguments should be indented too and have the comma prepended.
|
||||
```c++
|
||||
myClass::myClass(int a, int b, int c, int d)
|
||||
@@ -182,7 +158,7 @@ myClass::myClass(int a, int b, int c, int d)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Enums. ###
|
||||
### 6. Enums. ###
|
||||
Enums should be vertical. This will allow for more easily readable diffs. The members should be indented.
|
||||
```c++
|
||||
enum Days
|
||||
@@ -197,7 +173,7 @@ enum Days
|
||||
};
|
||||
```
|
||||
|
||||
### 6. Names. ###
|
||||
### 7. Names. ###
|
||||
All names should be camelCased.
|
||||
|
||||
#### a. Type names and namespaces ####
|
||||
@@ -231,73 +207,40 @@ class MyClass
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Header inclusion order. ###
|
||||
The headers should be placed in the following group order:
|
||||
1. Module header (in .cpp)
|
||||
2. C++ Standard Library headers
|
||||
3. System headers
|
||||
4. Boost library headers
|
||||
5. Libtorrent headers
|
||||
6. Qt headers
|
||||
7. qBittorrent's own headers, starting from the *base* headers.
|
||||
|
||||
The headers should be ordered alphabetically within each group.
|
||||
If there are conditionals for the same header group, then put them at the bottom of the respective group.
|
||||
If there are conditionals that contain headers from several different header groups, then put them above the "qBittorrent's own headers" group.
|
||||
|
||||
One exception is the header containing the library version (for example, QtGlobal), this particular header isn't constrained by the aforementioned order.
|
||||
### 8. Header inclusion order. ###
|
||||
The headers should be placed in the following order:
|
||||
1. Module header (in .cpp)
|
||||
2. System/Qt/Boost etc. headers (splitted in subcategories if you have many).
|
||||
3. Application headers, starting from *Base* headers.
|
||||
|
||||
The headers should be ordered alphabetically within each group (subgroup).<br/>
|
||||
<br/>
|
||||
Example:
|
||||
```c++
|
||||
// file: examplewidget.cpp
|
||||
// examplewidget.cpp
|
||||
|
||||
// Module header
|
||||
#include "examplewidget.h"
|
||||
|
||||
// exceptions, headers containing version number
|
||||
#include <boost/version.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
#include <QtGlobal>
|
||||
|
||||
// C++ Standard Library headers
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef Q_OS_WIN // conditional
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
// System headers
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
// Boost library headers
|
||||
#include <boost/circular_buffer.hpp>
|
||||
|
||||
// Libtorrent headers
|
||||
#include <libtorrent/session.hpp>
|
||||
|
||||
// Qt headers
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#ifdef Q_OS_MAC // conditional
|
||||
#include <QFont>
|
||||
#endif
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
// conditional that contains headers from several different header groups
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <memory>
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
|
||||
// qBittorrent's own headers
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "anothermodule.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "ui_examplewidget.h"
|
||||
|
||||
```
|
||||
|
||||
### 8. Include guard. ###
|
||||
### 9. Include guard. ###
|
||||
`#pragma once` should be used instead of "include guard" in new code:
|
||||
```c++
|
||||
// examplewidget.h
|
||||
@@ -313,7 +256,7 @@ class ExampleWidget : public QWidget
|
||||
|
||||
```
|
||||
|
||||
### 9. Misc. ###
|
||||
### 10. Misc. ###
|
||||
|
||||
* Line breaks for long lines with operation:
|
||||
|
||||
@@ -389,16 +332,5 @@ i++, j--; // No
|
||||
|
||||
* Method definitions aren't allowed in header files
|
||||
|
||||
### 10. Git commit message ###
|
||||
1. Limit the subject line to 50 characters. Subject should contain only the very essence of the changes (you should avoid extra details and internals)
|
||||
2. Separate subject from body with a blank line
|
||||
3. Capitalize the subject line
|
||||
4. Do not end the subject line with a period
|
||||
5. Use the imperative mood in the subject line (it's like you're ordering the program to do something (e.g. "Don't create temporary substrings")
|
||||
6. Wrap the body at 72 characters
|
||||
7. Use the body to explain what and why vs. how
|
||||
8. If commit fixes a reported issue, mention it in the message body (e.g. `Closes #4134.`)
|
||||
|
||||
### 11. Not covered above ###
|
||||
If something isn't covered above, just follow the same style the file you are editing has.
|
||||
*This guide is not exhaustive and the style for a particular piece of code not specified here will be determined by project members on code review.*
|
||||
### 10. Not covered above ###
|
||||
If something isn't covered above, just follow the same style the file you are editing has. If that particular detail isn't present in the file you are editing, then use whatever the rest of the project uses.
|
||||
|
||||
242
CONTRIBUTING.md
242
CONTRIBUTING.md
@@ -1,221 +1,35 @@
|
||||
# How to contribute to qBittorrent
|
||||
# Filing an issue
|
||||
|
||||
### Must read
|
||||
* If you aren't sure, you can ask on the [**forum**](http://forum.qbittorrent.org) or read our [**wiki**](http://wiki.qbittorrent.org) first.
|
||||
* Do a quick **search**. Others might already reported the issue.
|
||||
* Write in **English**!
|
||||
* Provide **version** information: (You can find version numbers at menu `Help -> About -> Libraries`)
|
||||
```
|
||||
qBittorrent:
|
||||
Qt:
|
||||
libtorrent:
|
||||
boost:
|
||||
OS version:
|
||||
```
|
||||
* Provide **steps** to reproduce the problem, it will be easier to pinpoint the fault.
|
||||
* **Screenshots**! A screenshot is worth a thousand words. just upload it. [(How?)](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests)
|
||||
|
||||
### Good to know
|
||||
* **Be patient**. The dev team is small and resource limited. Devs finding their free time, analyzing the problem and fixing the issue, it all takes time. :clock3:
|
||||
* If you can code, why not become a **contributor** by fixing the issue and open a pull request? :wink:
|
||||
* Harsh words or threats won't help your situation. What's worse, your complain will (very likely) to be **ignored**. :fearful:
|
||||
|
||||
There are three main ways to contribute to the project.
|
||||
Read the respective section to find out more.
|
||||
|
||||
### Table Of Contents
|
||||
|
||||
* **[Bug reporting etiquette](#bug-reporting-etiquette)**
|
||||
|
||||
|
||||
* **[Submitting an issue/bug report](#submitting-an-issuebug-report)**
|
||||
* [What is an actual bug report?](#what-is-an-actual-bug-report)
|
||||
* [Before submitting a bug report](#before-submitting-a-bug-report)
|
||||
* [Steps to ensure a good bug report](#steps-to-ensure-a-good-bug-report)
|
||||
|
||||
|
||||
* **[Suggesting enhancements/feature requests](#suggesting-enhancementsfeature-requests)**
|
||||
* [Before submitting an enhancement/feature request](#before-submitting-an-enhancementfeature-request)
|
||||
* [Steps to ensure a good enhancement/feature suggestion](#steps-to-ensure-a-good-enhancementfeature-suggestion)
|
||||
|
||||
|
||||
* **[Opening a pull request](#opening-a-pull-request)**
|
||||
* [Must read](#must-read)
|
||||
* [Good to know](#good-to-know)
|
||||
|
||||
# Bug reporting etiquette
|
||||
|
||||
* Issues, pull requests, and comments must always be in **English.**
|
||||
|
||||
* This project is supported by volunteers, do not expect "customer support"-style interaction.
|
||||
|
||||
* **Be patient.** The development team is small and resource limited. Developers and contributors take from their free time to analyze the problem and fix the issue. :clock3:
|
||||
|
||||
* Harsh words or threats won't help your situation. What's worse, your complain will (very likely) be **ignored.** :fearful:
|
||||
|
||||
# Submitting an issue/bug report
|
||||
|
||||
This section guides you through submitting an issue/bug report for qBittorrent.
|
||||
|
||||
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
|
||||
|
||||
Make sure to follow these rules carefully when submitting a bug report. Failure to do so will result in the issue being closed.
|
||||
|
||||
## What is an actual bug report?
|
||||
|
||||
Developers and contributors are not supposed to deal with issues for which little to no investigation to find the actual cause of a purported issue was made by the reporter.
|
||||
|
||||
Positive contributions are those which are reported with efforts to find the actual cause of an issue, or at the very least efforts were made to narrow it as much as possible.
|
||||
|
||||
Requiring people to investigate as much as possible before opening an issue will more than likely avoid burdening the project with invalid issues or issues unrelated to qBittorrent.
|
||||
|
||||
The following are _not_ bug reports. **Check the [wiki][wiki-url], [forum][forum-url] or other places for help and support for issues like these**:
|
||||
|
||||
- Explanation of qBittorrent options (see [wiki][wiki-url]).
|
||||
- Help with WebUI setup.
|
||||
- Help with embedded tracker setup.
|
||||
- Help about BitTorrent in general.
|
||||
- Issues with specific search plugins.
|
||||
- Asking for specific builds of qBittorrent other than the current one. You can install older releases at your own risk or for regression testing purposes. Previous Windows and macOS builds are available [here][builds-url].
|
||||
- If you want older Linux builds, you will have to compile them yourself from the corresponding commits, or ask someone on the [forum][forum-url] to do it for you.
|
||||
- Possibly others. Read on and use common sense.
|
||||
|
||||
The issue tracker is for provable issues only: You will have to make the case that the issue is really with qBittorrent and not something else on your side.
|
||||
|
||||
To make a case means to provide detailed steps so that anybody can reproduce the issue.
|
||||
Be sure to rule out that the issue is not caused by something specific on your side.
|
||||
|
||||
Issue reports for bugs that apparently aren't easily reproducible or that you can't figure out what triggers it even though you tried are OK.
|
||||
|
||||
Any issue opened without effort to provide the required details for developers, contributors or anybody else to reproduce the problem will be closed as invalid.
|
||||
For example:
|
||||
- Crash reports with just a stack trace.
|
||||
- Speculated performance issues that do not come with actual profiling data + analysis supporting the claim.
|
||||
|
||||
## Before submitting a bug report
|
||||
|
||||
- **Do some basic troubleshooting (examples)**:
|
||||
- Restart qBittorrent.
|
||||
- Restart your PC.
|
||||
- Update your OS (e.g. Windows updates).
|
||||
- Update your network card drivers.
|
||||
- Fully reinstall qBittorrent.
|
||||
- etc...
|
||||
- Make sure the problem is not caused by anti-virus or other program messing with your files.
|
||||
- Check if you can reproduce the problem in the latest version of qBittorrent.
|
||||
- **Check [forum][forum-url] and [wiki][wiki-url].** You might be able to find the cause of the problem and fix things yourself.
|
||||
- **Check if the issue exists already in the issue tracker.**
|
||||
- If it does and the issue is still open, add a comment to the existing issue instead of opening a new one.
|
||||
- If you find a Closed issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
|
||||
- If the issue is with the search functionality:
|
||||
- **Make sure you have [`python`][python-url] installed correctly (remember the search functionality requires a working python installation).**
|
||||
- Make sure it is in fact a problem with the search functionality itself, and not a problem with the plugins. If something does not work properly with the search functionality, the first step is to rule out search plugin-related issues.
|
||||
- For search plugin issues, report on the respective search plugin support page, or at [qbittorrent/search-plugins][search-plugins-url].
|
||||
|
||||
## Steps to ensure a good bug report
|
||||
|
||||
**Follow these guidelines** in order to provide as much useful information as possible right away. Not all of them are applicable to all issues, but you are expected to follow most of these steps (use common sense).
|
||||
Otherwise, we've noticed that a lot of your time (and the developers') gets thrown away on exchanging back and forth to get this information.
|
||||
|
||||
* Use a **clear and descriptive title** for the issue to identify the problem.
|
||||
|
||||
* Post only **one specific issue per submission.**
|
||||
|
||||
* **Fill out the issue template properly.**
|
||||
|
||||
- **Make sure you are using qBittorrent on a supported platform.** Do not submit issues which can only be reproduced on beta/unsupported releases of supported operating systems (e.g. Windows 10 Insider, Ubuntu 12.04 LTS, etc).
|
||||
These are unstable/unsupported platforms, and in all likelihood, whatever the issue is, it is not related to qBittorrent.
|
||||
|
||||
* **Specify the OS you're using, its version and architecture.**
|
||||
* Examples: Windows 8.1 32-bit, Linux Mint 17.1 64-bit, Windows 10 Fall creators Update 64-bit, etc.
|
||||
|
||||
|
||||
* **Report only if you run into the issue with an official stable release, a beta release, or with the most recent upstream changes (in this last case specify the specific commit you are on).** (beta testing is encouraged :smile:). We do not provide support for bugs on unofficial Windows builds.
|
||||
|
||||
* **Specify the version of qBittorrent** you are using, as well as its **architecture** (x86 or x64) and its **libraries' versions** (Help -> About -> Libraries).
|
||||
|
||||
* Specify **how you installed**:
|
||||
- Linux: either from the PPA, your distribution's repositories, or compiled from source, or even possibly third-party repositories.
|
||||
- Windows: either from the installer, or compiled from source, or even possibly third-party repositories.
|
||||
- macOS: either from the installer, or compiled from source, or even possibly third-party repositories.
|
||||
|
||||
|
||||
* **Describe the exact steps which reproduce the problem in as many details as possible.**
|
||||
- For example, start by explaining how you started qBittorrent, e.g. was it via the terminal? Desktop icon? Did you start it as root or normal user?
|
||||
- **When listing steps, don't just say what you did, but explain how you did it.**
|
||||
- For example, if you added a torrent for download, did you do so via a `.torrent` file or via a magnet link? If it was with a torrent file did you do so by dragging the torrent file from the file manager to the transfer list, or did you use the "Add Torrent File" in the Top Bar?
|
||||
- Describe the behavior you observed after following the steps and point out what exactly is the problem with that behavior; this is what we'll be looking for after executing the steps.
|
||||
|
||||
|
||||
* **Explain which behavior you expected to see instead** and why.
|
||||
|
||||
* Use **screenshots/animated GIFs to help describe the issue** whenever appropriate [(How?)][attachments-howto-url].
|
||||
|
||||
* If the problem wasn't triggered by a specific action, describe what you were doing before the problem happened.
|
||||
|
||||
* **If you are reporting that qBittorrent crashes**, include the stack trace in the report; include it in a code block, a file attachment, or put it in a gist and provide link to that gist.
|
||||
|
||||
* **For performance-related issues**, include as much profiling data as you can (resource usage graphs, etc).
|
||||
|
||||
* Paste the **qBittorrent log** (or put the contents of the log in a gist and provide a link to the gist). The log can be viewed in the GUI (View -> Log -> tick all boxes). If you can't do that, the file is at:
|
||||
- Linux: `~/.local/share/data/qBittorrent/logs/qBittorrent.log`
|
||||
- Windows: `%LocalAppData%\qBittorrent\logs`
|
||||
- macOS: `~/Library/Application Support/qBittorrent/qBittorrent.log`
|
||||
|
||||
|
||||
* **Do NOT post comments like "+1" or "me too!"** without providing new relevant info on the issue. Using the built-in reactions is OK though. Remember that you can use the "subscribe" button to receive notifications of that report without having to comment first.
|
||||
|
||||
* If there seems to be an **issue with specific torrent files/magnet links**:
|
||||
- Don't post private `.torrent` files/magnet links, or ones that point to copyrighted content. If you are willing, offer to email a link or the `.torrent` file itself to whoever developer is debugging it and requests it.
|
||||
- Make sure you can't reproduce the problem with another client, to rule out the possibility that the issue is with the `.torrent` file/magnet link itself.
|
||||
|
||||
|
||||
* A screenshot, transcription or file upload of any of **qBittorrent's preferences that differ from the defaults.** Please include everything different from the defaults whether or not it seems relevant to your issue.
|
||||
|
||||
* **Attachment rules**:
|
||||
- Short logs and error messages can be pasted as quotes/code whenever small enough; otherwise make a gist with the contents and post the link to the gist.
|
||||
- Avoid linking/attaching impractical file formats such as PDFs/Word documents with images. If you want to post an image, just post the image.
|
||||
|
||||
### Provide more context by answering these questions (if applicable):
|
||||
|
||||
- Can you **reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens (e.g. only happens with extremely large torrents/only happens after qBittorrent is open for more than 2 days/etc...)
|
||||
|
||||
- Did the problem start happening recently (e.g. after updating to a new version of qBittorrent) or was this always a problem?
|
||||
|
||||
- If the problem started happening recently, can you reproduce the problem in an older version of qBittorrent?
|
||||
|
||||
- Are you saving files locally (in a disk in your machine), or are you saving them remotely (e.g. network drives)?
|
||||
|
||||
- Are you using qBittorrent with multiple monitors? If so, can you reproduce the problem when you use a single monitor?
|
||||
|
||||
Good read: [How to Report Bugs Effectively][howto-report-bugs-url]
|
||||
|
||||
# Suggesting enhancements/feature requests
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for qBittorrent, including completely new features and minor improvements to existing functionality.
|
||||
|
||||
Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
|
||||
|
||||
## Before submitting an enhancement/feature request
|
||||
|
||||
* Check the [wiki][wiki-url] and [forum][forum-url] for tips — you might discover that the enhancement is already available.
|
||||
* Most importantly, check if you're using the latest version of qBittorrent and if you can get the desired behavior by changing qBittorrent's settings.
|
||||
* Check in the [releases][releases-url] page or on the [forum][forum-url], see if there's already a alpha/beta version with that enhancement.
|
||||
* Perform a cursory search to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
|
||||
## Steps to ensure a good enhancement/feature suggestion
|
||||
|
||||
- Specify which version of qBittorrent you're using.
|
||||
- Specify the name and version of the OS you're using.
|
||||
- Provide a step-by-step description of the suggested enhancement in as many details as possible.
|
||||
- Describe the current behavior and explain which behavior you expected to see instead and why.
|
||||
- Include screenshots and animated GIFs which help you demonstrate the steps or point out the part of qBittorrent which the suggestion is related to.
|
||||
- If this enhancement exists in other BitTorrent clients, list those clients.
|
||||
|
||||
# Opening a pull request
|
||||
|
||||
### Must read
|
||||
* Read our [**coding guidelines**][coding-guidelines-url]. There are some scripts to help you: [uncrustify script][uncrustify-script-url], [astyle script][astyle-script-url], [(related thread)][coding-guidelines-thread-url].
|
||||
* Keep the title **short** and provide a **clear** description about what your pull request does.
|
||||
* Provide **screenshots** for UI related changes.
|
||||
* Keep your git commit history **clean** and **precise.** Refer to the section about "Git commit messages" in the [**coding guidelines**][coding-guidelines-url].
|
||||
* If your commit fixes a reported issue (for example #4134), add the following message to the commit `Closes #4134.`. Example [here][commit-message-fix-issue-example-url].
|
||||
* Read our [**coding guidelines**](https://github.com/qbittorrent/qBittorrent/blob/master/CODING_GUIDELINES.md). There are some scripts to help you: [uncrustify script](https://raw.githubusercontent.com/qbittorrent/qBittorrent/master/uncrustify.cfg), [astyle script](https://gist.github.com/Chocobo1/539cee860d1eef0acfa6), [(related thread)](https://github.com/qbittorrent/qBittorrent/issues/2192).
|
||||
* Keep the title **short** and provide a **clear** description about what your pull request does.
|
||||
* Provide **screenshots** for UI related changes.
|
||||
* Keep your git commit history **clean** and **precise**. Commits like `xxx fixup` should not appear.
|
||||
* If your commit fix a reported issue (for example #4134), add the following message to the commit `Closes #4134.`. Example [here](https://github.com/qbittorrent/qBittorrent/commit/a74bac20c4e8de9776bf9bb77fdc7526135d1988).
|
||||
|
||||
### Good to know
|
||||
* **Search** pull request history! Others might have already implemented your idea and it is waiting to be merged (or got rejected already). Save your precious time by doing a search first.
|
||||
* When resolving merge conflicts, do `git rebase <target_branch_name>`, don't do `git pull`. Then you can start fixing the conflicts. Here is a good explanation: [link][merging-vs-rebasing-url].
|
||||
|
||||
[astyle-script-url]: https://gist.github.com/Chocobo1/539cee860d1eef0acfa6
|
||||
[attachments-howto-url]: https://help.github.com/articles/file-attachments-on-issues-and-pull-requests
|
||||
[coding-guidelines-url]: https://github.com/qbittorrent/qBittorrent/blob/master/CODING_GUIDELINES.md
|
||||
[coding-guidelines-thread-url]: https://github.com/qbittorrent/qBittorrent/issues/2192
|
||||
[commit-message-fix-issue-example-url]: https://github.com/qbittorrent/qBittorrent/commit/c07cd440cd46345297debb47cb260f8688975f50
|
||||
[forum-url]: http://forum.qbittorrent.org/
|
||||
[howto-report-bugs-url]: https://www.chiark.greenend.org.uk/~sgtatham/bugs.html
|
||||
[merging-vs-rebasing-url]: https://www.atlassian.com/git/tutorials/merging-vs-rebasing
|
||||
[python-url]: https://www.python.org/
|
||||
[releases-url]: https://github.com/qbittorrent/qBittorrent/releases
|
||||
[search-plugins-url]: https://github.com/qbittorrent/search-plugins
|
||||
[uncrustify-script-url]: https://raw.githubusercontent.com/qbittorrent/qBittorrent/master/uncrustify.cfg
|
||||
[wiki-url]: https://github.com/qbittorrent/qBittorrent/wiki
|
||||
[builds-url]: https://sourceforge.net/projects/qbittorrent/files/
|
||||
* **Search** pull request history! Others might already implemented your idea and is waiting to be merged (or got rejected already). Save your precious time by doing a search first.
|
||||
* When resolving merge conflicts, do `git rebase <target_branch_name>`, don't do `git pull`. Then you can start fixing the conflicts. Here is a good explanation: [link](https://www.atlassian.com/git/tutorials/merging-vs-rebasing).
|
||||
|
||||
142
Changelog
142
Changelog
@@ -1,103 +1,3 @@
|
||||
* Sun May 27 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.1
|
||||
- FEATURE: Add 'Moving' state for torrents being relocated/moved (sledgehammer999)
|
||||
- FEATURE: Show rechecking progress (sledgehammer999)
|
||||
- FEATURE: Add option to remember last used save path (glassez)
|
||||
- FEATURE: Torrent name is also renamed if the content was renamed in the "Add New Torrent" dialog (glassez)
|
||||
- FEATURE: Relax behavior of "Download first and last piece first". It applies to all files and not only to the previewable. (Chocobo1)
|
||||
- BUGFIX: Fix issues with translatable strings (Chocobo1)
|
||||
- BUGFIX: Fix displayed tracker messages (Chocobo1)
|
||||
- BUGFIX: Make settings file recovery more robust (Chocobo1)
|
||||
- BUGFIX: Retry saving settings when operation failed (Chocobo1)
|
||||
- BUGFIX: Log successful torrent move (sledgehammer999)
|
||||
- BUGFIX: Fix deletion of old logs (sledgehammer999)
|
||||
- BUGFIX: Delete non-commited fastresume files (sledgehammer999)
|
||||
- BUGFIX: Don't migrate torrents that have newer fastresumes (sledgehammer999)
|
||||
- BUGFIX: Fix adding multiple torrents at once from WebUI (glassez)
|
||||
- BUGFIX: Improve "Run External Program" behavior. On Windows, a backslash isn't appended to paths from path variables (Chocobo1)
|
||||
- BUGFIX: Suppress multiple I/O errors for the same torrent (sledgehammer999)
|
||||
- BUGFIX: Replace raster qbt logo with vector version (Chocobo1)
|
||||
- WEBUI: Fix wrong API method names (glassez)
|
||||
- WEBUI: Filter torrent info endpoint by hashes (Marcel Petersen)
|
||||
- WEBUI: Fix invalid API calls in WebUI (glassez)
|
||||
- WEBUI: Improve legacy API params handling (glassez)
|
||||
- WEBUI: Fix params handling for some legacy API methods (glassez)
|
||||
- WEBUI: Apply locale changes immediately in WebUI (Chocobo1)
|
||||
- WEBUI: Use 32px icons for favicon (Chocobo1)
|
||||
- WEBUI/RSS: Properly set RSS settings via API (glassez)
|
||||
- RSS: Fix auto-downloading rule when Smart filter with regular Episode filter are used (glassez)
|
||||
- RSS: Make "Ignoring days" to behave like other filters (glassez)
|
||||
- RSS: Place "Use Smart Episode Filter" more correctly (glassez)
|
||||
- RSS: Use RSS feed update time as a fallback (glassez)
|
||||
- COSMETIC: Fix Stats dialog size (sledgehammer999)
|
||||
- MACOS: Fix GUI scaling factor on macOS (Chocobo1)
|
||||
- WINDOWS: Update icons (adem4ik)
|
||||
- LINUX: Fix open destination folder with Nautilus > 3.28 (Evgeny Lensky)
|
||||
- OTHER: Code improvements and refactoring (thalieht, Nick Korotysh, Chocobo1)
|
||||
|
||||
* Sat May 05 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.0
|
||||
- FEATURE: Add "Coalesce reads & writes" checkbox in advanced options (Chocobo1)
|
||||
- FEATURE: Smart Filter for RSS (Stephen Dawkins)
|
||||
- FEATURE: Possibility to configure at which speed a torrent is considered slow (thalieht)
|
||||
- FEATURE: When creating a torrent you can choose to preserve the file order (toster, Chocobo1)
|
||||
- FEATURE: A new, redesigned and refactored WebAPI (glassez)
|
||||
- BUGFIX: Redefine CacheStatus.readRatio field. (Chocobo1)
|
||||
- BUGFIX: Clarify some terms in stats dialog (Chocobo1)
|
||||
- BUGFIX: Fix possible crash when using both share limits (thalieht)
|
||||
- BUGFIX: Disable options when `Disable connections not supported by proxies` is enabled (Thomas Piccirello)
|
||||
- BUGFIX: Add link to an explanation of `Disable connections not supported by proxies` (Thomas Piccirello)
|
||||
- BUGFIX: Fix typo in a log message (Andrei Stepanov)
|
||||
- BUGFIX: Fix loading very large torrents. Closes #8449. (Chocobo1)
|
||||
- BUGFIX: Fix reverting backslashes to slashes in run external program. Closes #7800 (Chocobo1)
|
||||
- BUGFIX: Use https for documentation links (Chocobo1)
|
||||
- BUGFIX: Use original scheme when downloading favicons (Chocobo1)
|
||||
- BUGFIX: Parse URL query string at application level (glassez)
|
||||
- BUGFIX: Properly reply to announce request (embedded tracker) (glassez)
|
||||
- BUGFIX: Add `Tags` parameter to "Run External Program" (Chocobo1)
|
||||
- BUGFIX: Fix various typos (Chocobo1)
|
||||
- BUGFIX: Refactor filesystem watcher. Delay before processing new files. (Chocobo1)
|
||||
- BUGFIX: Don't strip empty arguments passed to external program. Closes #8454. (Chocobo1)
|
||||
- BUGFIX: Stop creating Download folder on start (Chocobo1)
|
||||
- BUGFIX: Avoid data corruption when rechecking paused torrents (sledgehammer999)
|
||||
- BUGFIX: Fix crashes due to invalid iterator use (Luís Pereira)
|
||||
- BUGFIX: Fix renaming completed files (Chocobo1)
|
||||
- BUGFIX: Fix path separator in log messages (Chocobo1)
|
||||
- WEBUI: Switch built-in Web UI html to HTML5 (glassez)
|
||||
- WEBUI: WebUI Save user's resized window sizes (Thomas Piccirello)
|
||||
- WEBUI: Make download + upload windows resizable (Thomas Piccirello)
|
||||
- WEBUI: Add option to show/hide webui status bar (Thomas Piccirello)
|
||||
- WEBUI: Add "Use proxy only for torrents" option to webui (Thomas Piccirello)
|
||||
- WEBUI: Various fixes in the html code (Thomas Piccirello)
|
||||
- WEBUI: Don't unselect selected torrents after a few seconds (Thomas Piccirello)
|
||||
- WEBUI: Enable Http/1.1 persistence connection (Chocobo1)
|
||||
- WEBUI: Format Read cache hits as percentage (Thomas Piccirello)
|
||||
- WEBUI: Re-order and rename stats (Thomas Piccirello)
|
||||
- WEBUI: Right align stat values (Thomas Piccirello)
|
||||
- WEBUI: Enable Statistics window to be scrolled and resized (Tom Piccirello)
|
||||
- WEBUI: Save WebUI Statistics window size (Thomas Piccirello)
|
||||
- WEBUI: Make WebUI iframe windows scrollable on iOS (Thomas Piccirello)
|
||||
- WEBUI: Remove unused CSS from WebUI login page (Thomas Piccirello)
|
||||
- WEBUI: Consolidate CSS into style.css (Thomas Piccirello)
|
||||
- WEBUI: Resolve JavaScript errors (Thomas Piccirello)
|
||||
- WEBUI: Fix spacing in login form(Thomas Piccirello)
|
||||
- WEBUI: Update WebUI to be more compliant with HTML5 standard (Chocobo1)
|
||||
- WEBUI: Update clipboard.js to v2.0.0 (Chocobo1)
|
||||
- WEBUI: Remove unused JavaScript library (Chocobo1)
|
||||
- WEBUI: Fix setting preferences via WebAPI (glassez)
|
||||
- WEBUI: Rename property to match its definition (Thomas Piccirello)
|
||||
- WEBUI: Add Limit Share Ratio context menu option (Thomas Piccirello)
|
||||
- RSS: Disable Auto TMM when RSS rule has save path (glassez)
|
||||
- RSS: Process loaded RSS articles in case of error (glassez)
|
||||
- RSS: Resolve (X)HTML entities in RSS content (glassez)
|
||||
- SEARCH: Improve Search handling (glassez)
|
||||
- SEARCH: Calculate supported categories based on selected plugin (Thomas Piccirello)
|
||||
- SEARCH: Fix memory leak (Chocobo1)
|
||||
- COSMETIC: Use spinbox suffix to display rate/time units (thalieht)
|
||||
- COSMETIC: Avoid showing an empty row in AdvancedSettings (Chocobo1)
|
||||
- OTHER: Various code optimizations and fixes (Luís Pereira, Chocobo1)
|
||||
- OTHER: Fix build when using Clang under CMake (Luís Pereira)
|
||||
- OTHER: Allow to disable Stacktrace support (Nick Korotysh)
|
||||
- OTHER: Use RNG provided by OS (Chocobo1)
|
||||
|
||||
* Fri Feb 16 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.4
|
||||
- FEATURE: Add source field in Torrent creator. Closes #7965. (Chocobo1)
|
||||
- FEATURE: Torrent creator: raise maximum piece size to 32 MiB (Chocobo1)
|
||||
@@ -222,7 +122,7 @@
|
||||
- BUGFIX: Optimize code for SpeedWidget. (dzmat)
|
||||
- BUGFIX: Disable processing events when adding torrents(prevents crashes). Closes #7436. (Chocobo1)
|
||||
- BUGFIX: Open links in browser. Closes #7651. (Chocobo1)
|
||||
- BUGFIX: Change default settings for tracker/tier announces to mimic μTorrent behavior. (sledgehammer999)
|
||||
- BUGFIX: Change default settings for tracker/tier announces to mimick μTorrent behavior. (sledgehammer999)
|
||||
- BUGFIX: Explicitly set UPnP state on start-up. Closes #7338. (Chocobo1)
|
||||
- BUGFIX: Include/print caught signal in stackdump (Chocobo1)
|
||||
- COSMETIC: Trackerlist: Set text alignment of columns with numbers to the right (thalieht)
|
||||
@@ -353,7 +253,7 @@
|
||||
- FEATURE: Use Ctrl+F to search torrents. Closes #5797. (Tim Delaney)
|
||||
- FEATURE: Transferlist: add hotkeys for double click and recheck selected torrents (thalieht)
|
||||
- FEATURE: Add hotkey for execution log tab, Trackerlist, Peerlist etc (thalieht)
|
||||
- FEATURE: Separate seeds from peers for DHT, PeX and LSD (thalieht)
|
||||
- FEATURE: Seperate seeds from peers for DHT, PeX and LSD (thalieht)
|
||||
- BUGFIX: Do not remove added files unconditionally. Closes #6248 (Eugene Shalygin)
|
||||
- BUGFIX: Ignore mouse wheel events in Advanced Settings. Closes #866. (Chocobo1)
|
||||
- BUGFIX: Add queue repair code. It should fix missing torrents after restarting. (Eugene Shalygin, nxd4)
|
||||
@@ -365,7 +265,7 @@
|
||||
- BUGFIX: TransferListWidget: keep columns width even if they are hidden on qBittorrent startup (unless something goes wrong) (thalieht)
|
||||
- BUGFIX: fix index overflow for torrents with invalid meta data or empty progress (Falco)
|
||||
- BUGFIX: Immediately update torrent_status after manipulating super seeding mode. Partially fixes #6072. (sledgehammer999)
|
||||
- BUGFIX: Use case-insensitive comparison for torrent content window. Closes #6327. (Chocobo1)
|
||||
- BUGFIX: Use case-insensitive comparsion for torrent content window. Closes #6327. (Chocobo1)
|
||||
- BUGFIX: Fixed sort order for datetime columns with empty values (closes #2988) (Vladimir Sinenko)
|
||||
- BUGFIX: Disable proxy in WebUI HTTP server. Closes #6349. (Eugene Shalygin)
|
||||
- COSMETIC: Use a disabled progressbar's palette for unselected files. (sledgehammer999)
|
||||
@@ -493,12 +393,12 @@
|
||||
- FEATURE: Add option to automatically remove .torrent files upon adding (Eugene Shalygin)
|
||||
- FEATURE: Add option to bind directly to an IP instead of using a network Interface (Sjoerd van der Berg, sledgehammer999)
|
||||
- FEATURE: Detailed tooltips on the progress and availability bars in the General button of each torrent. (Eugene Shalygin)
|
||||
- FEATURE: Let user able to specify a filter when choosing an IP filter file (Chocobo1)
|
||||
- FEATURE: Let user able to specifiy a filter when choosing an IP filter file (Chocobo1)
|
||||
- FEATURE: Improve usability of "Run External Program". Users can write (platform dependent) shell scripts now. (Chocobo1)
|
||||
- PERFORMANCE: Optimize drawing in speed graph (Anton Lashkov, Chocobo1)
|
||||
- BUGFIX: Fix memory leak. (sledgehammer999)
|
||||
- BUGFIX: Fix resizing bug in "add torrent dialog". Closes #5036. (Chocobo1)
|
||||
- BUGFIX: Fix qBittorrent doesn't exit immediately when "all downloads are done -> exit" option enabled. (glassez, Chocobo1)
|
||||
- BUGFIX: Fix qBittorrent doesn't exit immediately when "all donwloads are done -> exit" option enabled. (glassez, Chocobo1)
|
||||
- BUGFIX: Display the filepath when a torrent fails to load. Closes #100 and #805. (sledgehammer999)
|
||||
- BUGFIX: Fix Add tracker dialog empty trackers (ngosang)
|
||||
- BUGFIX: Fix Add tracker dialog URL download (ngosang)
|
||||
@@ -551,7 +451,7 @@
|
||||
- OTHER: Enable access to shutdown functions when configured with `--disable-gui` option (Chocobo1)
|
||||
- OTHER: Delete Import Torrent Dialog. Just use the "add new torrent" dialog. (glassez)
|
||||
- OTHER: Optimize code for natural sorting (Chocobo1)
|
||||
- OTHER: Use new alert dispatching API for libtorrent 1.1.x (glassez)
|
||||
- OTHER: Use new alert dispathing API for libtorrent 1.1.x (glassez)
|
||||
- OTHER: Fix gcc 6 compilation with qmake. See #5237. (sledgehammer999)
|
||||
|
||||
* Tue Mar 29 2016 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.4
|
||||
@@ -875,7 +775,7 @@
|
||||
- SEARCH: Fix thepiratebay. Closes #3012 (ngosang)
|
||||
- SEARCH: Improve torrentz engine to return more results (ngosang)
|
||||
- SEARCH: Change width of columns in search tab. Closes #764 (ngosang)
|
||||
- SEARCH: Make strings translatable in search engine (ngosang)
|
||||
- SEARCH: Make strings translatable in seach engine (ngosang)
|
||||
- SEARCH: Aborting search engine process during closure. Close #2671 (DoumanAsh)
|
||||
- SEARCH: Perform searches in parallel (DoumanAsh)
|
||||
- SEARCH: Add Demonoid search engine (ngosang)
|
||||
@@ -1042,7 +942,7 @@
|
||||
- WEBUI: Removed broken 'Documentation'. Improves fix for #1343 (Benjamin Hutchins)
|
||||
- WEBUI: Removed essentially useless 'Visit website' iframe and changed it to a regular link. Improves fix for #1343 (Benjamin Hutchins)
|
||||
- BUGFIX: Fix RSS feed icon. The tmp file gets deleted in the feed destructor. Closes #1639 (sledgehammer999)
|
||||
- BUGFIX: fix issue #1674: AddNewTorrentDialog is shown again and again even if checkbox "don't ask me again" is set (Ivan Sorokin)
|
||||
- BUGFIX: fix issue #1674: AddNewTorrentDialog is shown again and again even if checkbox "dont ask me again" is set (Ivan Sorokin)
|
||||
- BUGFIX: Don't show availability bar for magnet links (Ivan Sorokin)
|
||||
- BUGFIX: Fix crash when the selected torrent disappears from the transfer list. Closes #1661 (sledgehammer999)
|
||||
- BUGFIX: Fix tracker announcing problem(hit-and-run) when many torrents are being active. Closes #1571 (sledgehammer999)
|
||||
@@ -1176,7 +1076,7 @@
|
||||
- FEATURE: Show external IP in the log. Closes #968. (sledgehammer999)
|
||||
- FEATURE: Enable gzip compression in the webui. It should be faster now. (sledgehammer999)
|
||||
- FEATURE: Torrents show more states(queued for checking, downloading metadata, allocating, checking resume). (sledgehammer999)
|
||||
- FEATURE: Re-enable "force reannounce" to all trackers. (sledgehammer999)
|
||||
- FEATURE: Reenable "force reannounce" to all trackers. (sledgehammer999)
|
||||
- FEATURE: Allow to clear the UI lock password. Closes #973. (sledgehammer999)
|
||||
- FEATURE: New translations: English(Australia) and English(United Kingdom)
|
||||
- BUGFIX: Expose all available translation in the WebUI. Closes #976. (sledgehammer999)
|
||||
@@ -1225,8 +1125,8 @@
|
||||
- OTHER: Make peer tab sortable by ip too (Gelmir)
|
||||
- OTHER: Translations moved to Transifex(https://www.transifex.com/projects/p/qbittorrent/)
|
||||
- OTHER: New Translation - Vietnamese (Anh Phan)
|
||||
- PERFORMANCE: Improve drawing speed of tranferlist when there are many torrents(>100)
|
||||
- PERFORMANCE: Improve drawing speed of peers list when there are many peers
|
||||
- PERFORMANCE: Impove drawing speed of tranferlist when there are many torrents(>100)
|
||||
- PERFORMANCE: Impove drawing speed of peers list when there are many peers
|
||||
|
||||
* Mon Jul 29 2013 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.0.11
|
||||
- FEATURE: Allow more fine tuning of upload slots. It should improve speed (sledgehammer999)
|
||||
@@ -1266,8 +1166,8 @@
|
||||
- LIBTORRENT: SOCKS5 fixes (0.16.10)
|
||||
- LIBTORRENT: Fix hanging issue on Windows when closing files (0.16.10)
|
||||
- LIBTORRENT: Cache can now be returned to the OS (0.16.10)
|
||||
- PERFORMANCE: Improve drawing speed of tranferlist when there are many torrents(>100) (sledgehammer999)
|
||||
- PERFORMANCE: Improve drawing speed of peers list when there are many peers (sledgehammer999)
|
||||
- PERFORMANCE: Impove drawing speed of tranferlist when there are many torrents(>100) (sledgehammer999)
|
||||
- PERFORMANCE: Impove drawing speed of peers list when there are many peers (sledgehammer999)
|
||||
|
||||
* Sat Mar 16 2013 - Christophe Dumez <chris@qbittorrent.org> - v3.0.9
|
||||
- BUGFIX: Raise qBittorrent windows when another instance is launched
|
||||
@@ -1291,7 +1191,7 @@
|
||||
- BUGFIX: Fix "Couldn't set environment variable..." message on start up (closes #245)
|
||||
- BUGFIX: Use right path separator in torrent addition dialog on Windows
|
||||
- BUGFIX: Fix "Set as default save path" setting (closes #254)
|
||||
- BUGFIX: Re-enable disk cache on Windows since the memory issue seems to be gone
|
||||
- BUGFIX: Reenable disk cache on Windows since the memory issue seems to be gone
|
||||
- BUGFIX: Fixed several search engine plugins and removed the dead ones
|
||||
- BUGFIX: Use https links in search plugins when possible
|
||||
- BUGFIX: Bump Mootools to v1.4.5 (Web UI)
|
||||
@@ -1427,7 +1327,7 @@
|
||||
- I18N: Add Georgian translation
|
||||
|
||||
* Sat Oct 29 2011 - Christophe Dumez <chris@qbittorrent.org> - v2.9.2
|
||||
- BUGFIX: Fix minimum dimensions for torrent addition dialog
|
||||
- BUGFIX: Fix mimimum dimensions for torrent addition dialog
|
||||
- BUGFIX: Remove dependency on boost-datetime
|
||||
- BUGFIX: Remove dependency on boost-filesystem (libtorrent v0.16.x)
|
||||
|
||||
@@ -1638,7 +1538,7 @@
|
||||
- BUGFIX: Update RSS feed as soon as feed downloader is enabled
|
||||
- BUGFIX: RSS Feed downloader ignores articles above maximum number of articles
|
||||
- BUGFIX: Fix possible bug when deleting a RSS folder
|
||||
- BUGFIX: Remove persistent data when a RSS feed is deleted
|
||||
- BUGFIX: Remove persistant data when a RSS feed is deleted
|
||||
- BUGFIX: RSS filters are now alphabetically sorted
|
||||
- BUGFIX: Fix crash when renaming currently displayed RSS filter
|
||||
- BUGFIX: Remove overwriting confirmation when exporting RSS filters since Qt takes care of it
|
||||
@@ -1674,7 +1574,7 @@
|
||||
- BUGFIX: Use the save path set in program preferences as a default in torrent addition dialog
|
||||
|
||||
* Fri Dec 18 2009 - Christophe Dumez <chris@qbittorrent.org> - v2.0.2
|
||||
- BUGFIX: Fix .qbittorrent folder not being created (critical bug introduced in v2.0.1 that makes qBittorrent unusable for new users)
|
||||
- BUGFIX: Fix .qbittorrent folder not being created (critical bug introduced in v2.0.1 that makes qBittorrent unusuable for new users)
|
||||
- BUGFIX: Fix RSS Feed downloader for some feeds
|
||||
- BUGFIX: Do not use home folder as a fallback when the save path is not accessible
|
||||
- BUGFIX: Fix Mininova, ThePirateBay search engine plugins
|
||||
@@ -1764,7 +1664,7 @@
|
||||
- BUGFIX: Fix trackers addition to torrents (bug introduced in v1.5.4)
|
||||
- BUGFIX: Suppress compilation warning regarding sortNewsList() not being used
|
||||
- BUGFIX: Make sure scan folder is different than qBittorrent backup directory to avoid torrents deletion
|
||||
- BUGFIX: Added safety mechanism which adds the torrents back to the list in case qbittorrent-resume.conf gets deleted or corrupted.
|
||||
- BUGFIX: Added safety mecanism which adds the torrents back to the list in case qbittorrent-resume.conf gets deleted or corrupted.
|
||||
|
||||
* Sun Oct 25 2009 - Christophe Dumez <chris@qbittorrent.org> - v1.5.4
|
||||
- BUGFIX: Updated man page
|
||||
@@ -1831,7 +1731,7 @@
|
||||
- FEATURE: Added right click menu in search engine to clear completion history
|
||||
- FEATURE: Allow to set a different port for DHT (UDP) than the one used for Bittorrent
|
||||
- FEATURE: Updated spoofing code to avoid trackers ban
|
||||
- BUGFIX: Provide more helpful explanation when an I/O error occurred
|
||||
- BUGFIX: Provide more helpful explanation when an I/O error occured
|
||||
- BUGFIX: Stop enforcing UTF-8 and use system locale instead
|
||||
- COSMETIC: Redesigned program preferences
|
||||
- COSMETIC: Updated icons set
|
||||
@@ -1849,7 +1749,7 @@
|
||||
- BUGFIX: Suppressed QLayout: Attempting to add QLayout "" to properties "properties" warning message when opening a properties dialog
|
||||
- BUGFIX: Fixed a little bug in search engine plugins helper file
|
||||
- BUGFIX: Fixed compilation problems with Qt 4.3
|
||||
- BUGFIX: Percentages no longer disappear with default cleanlooks style
|
||||
- BUGFIX: Percentages no longer disapear with default cleanlooks style
|
||||
- BUGFIX: Cleanly fixed popup menus position in lists (no more workarounds)
|
||||
- BUGFIX: Fixed memory leak in search engine
|
||||
- BUGFIX: Torrents with an infinite ratio are no longer affected by ratio_limit set in program preferences
|
||||
@@ -2094,7 +1994,7 @@
|
||||
- FEATURE: Number of complete/incomplete sources are now displayed in download list for each torrent
|
||||
- FEATURE: Implemented close to systray
|
||||
- FEATURE: Added Autocompletion to search engine
|
||||
- FEATURE: Split BT & GUI parts (huge code rewriting & optimization)
|
||||
- FEATURE: Splitted BT & GUI parts (huge code rewriting & optimization)
|
||||
- FEATURE: New parameters for configure file to point to custom locations for libtorrent/libcurl
|
||||
- FEATURE: Update application style according to the system (WindowsXP, MacOS, X11)
|
||||
- BUGFIX: Two torrents can now have the same name although they are different (use their hash)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# - macro similar to target_link_libraries, which links Qt components
|
||||
# names of the components are passed in Qt4/Qt5 agnostic way (Core, DBus, Xml...)
|
||||
# names of the components are pased in Qt4/Qt5 agnostic way (Core, DBus, Xml...)
|
||||
# and the macro links Qt4 ones if QT4_FOUND is set or Qt5 ones if not
|
||||
|
||||
macro (target_link_qt_components target)
|
||||
|
||||
@@ -11,17 +11,18 @@ macro(qbt_set_compiler_options)
|
||||
#-Wshadow -Wconversion ?
|
||||
set(_GCC_COMMON_C_AND_CXX_FLAGS "-Wall -Wextra"
|
||||
"-Wfloat-equal -Wcast-qual -Wcast-align"
|
||||
"-Wsign-conversion -Winvalid-pch -Wno-long-long"
|
||||
#"-fstack-protector-all"
|
||||
#"-Werror -Wno-error=deprecated-declarations"
|
||||
"-Wsign-conversion -Winvalid-pch -Werror=return-type -Wno-long-long"
|
||||
# -fstack-protector-all
|
||||
"-Werror -Wno-error=deprecated-declarations"
|
||||
)
|
||||
set(_GCC_COMMON_CXX_FLAGS "-fexceptions -frtti"
|
||||
"-Woverloaded-virtual -Wold-style-cast"
|
||||
set (_GCC_COMMON_CXX_FLAGS "-fexceptions -frtti"
|
||||
"-Woverloaded-virtual -Wold-style-cast -Wstrict-null-sentinel"
|
||||
"-Wnon-virtual-dtor -Wfloat-equal -Wcast-qual -Wcast-align"
|
||||
#"-Weffc++"
|
||||
#"-Werror -Wno-error=cpp"
|
||||
"-Werror=overloaded-virtual"
|
||||
# "-Weffc++"
|
||||
"-Werror -Wno-error=cpp"
|
||||
# we should modify code to make these ones obsolete
|
||||
#"-Wno-error=sign-conversion -Wno-error=float-equal"
|
||||
"-Wno-error=sign-conversion -Wno-error=float-equal"
|
||||
)
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
|
||||
@@ -53,20 +54,6 @@ macro(qbt_set_compiler_options)
|
||||
endif(${GLIBC_VERSION})
|
||||
endif (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
# Clang 5.0 still doesn't support -Wstrict-null-sentinel
|
||||
check_cxx_compiler_flag(-Wstrict-null-sentinel _STRICT_NULL_SENTINEL_IS_SUPPORTED)
|
||||
if (_STRICT_NULL_SENTINEL_IS_SUPPORTED)
|
||||
list(APPEND _GCC_COMMON_CXX_FLAGS "-Wstrict-null-sentinel")
|
||||
endif (_STRICT_NULL_SENTINEL_IS_SUPPORTED)
|
||||
|
||||
# Code should be improved to render this not needed
|
||||
list(APPEND _GCC_COMMON_CXX_FLAGS "-Wno-error=unused-function -Wno-error=inconsistent-missing-override")
|
||||
else ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
# GCC supports it
|
||||
list(APPEND _GCC_COMMON_CXX_FLAGS "-Wstrict-null-sentinel")
|
||||
endif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
|
||||
string(REPLACE ";" " " _GCC_COMMON_C_AND_CXX_FLAGS_STRING "${_GCC_COMMON_C_AND_CXX_FLAGS}")
|
||||
string(REPLACE ";" " " _GCC_COMMON_CXX_FLAGS_STRING "${_GCC_COMMON_CXX_FLAGS}")
|
||||
|
||||
|
||||
@@ -58,4 +58,4 @@ DEFINES += BOOST_USE_WINAPI_VERSION=0x0501
|
||||
#DEFINES += TORRENT_LINKING_SHARED
|
||||
|
||||
# Enable stack trace support
|
||||
CONFIG += stacktrace
|
||||
CONFIG += strace_win
|
||||
|
||||
68
configure
vendored
68
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.1.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.0.4.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -580,8 +580,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.1.1'
|
||||
PACKAGE_STRING='qbittorrent v4.1.1'
|
||||
PACKAGE_VERSION='v4.0.4'
|
||||
PACKAGE_STRING='qbittorrent v4.0.4'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -717,7 +717,6 @@ enable_dependency_tracking
|
||||
enable_silent_rules
|
||||
with_qtsingleapplication
|
||||
enable_debug
|
||||
enable_stacktrace
|
||||
enable_gui
|
||||
enable_systemd
|
||||
enable_webui
|
||||
@@ -1297,7 +1296,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures qbittorrent v4.1.1 to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.0.4 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1368,7 +1367,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.1.1:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.0.4:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1383,7 +1382,6 @@ Optional Features:
|
||||
--enable-silent-rules less verbose build output (undo: "make V=1")
|
||||
--disable-silent-rules verbose build output (undo: "make V=0")
|
||||
--enable-debug Enable debug build
|
||||
--enable-stacktrace Enable stacktrace feature (default=auto)
|
||||
--disable-gui Disable the GUI for headless running. Disables
|
||||
QtDBus and the GeoIP Database.
|
||||
--enable-systemd Install the systemd service file (headless only).
|
||||
@@ -1503,7 +1501,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.1.1
|
||||
qbittorrent configure v4.0.4
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -1642,7 +1640,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by qbittorrent $as_me v4.1.1, which was
|
||||
It was created by qbittorrent $as_me v4.0.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3820,7 +3818,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.1.1'
|
||||
VERSION='v4.0.4'
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
@@ -4191,14 +4189,6 @@ else
|
||||
fi
|
||||
|
||||
|
||||
# Check whether --enable-stacktrace was given.
|
||||
if test "${enable_stacktrace+set}" = set; then :
|
||||
enableval=$enable_stacktrace;
|
||||
else
|
||||
enable_stacktrace=auto
|
||||
fi
|
||||
|
||||
|
||||
# Check whether --enable-gui was given.
|
||||
if test "${enable_gui+set}" = set; then :
|
||||
enableval=$enable_gui;
|
||||
@@ -4399,39 +4389,6 @@ $as_echo "$enable_debug" >&6; }
|
||||
as_fn_error $? "Unknown option \"$enable_debug\". Use either \"yes\" or \"no\"." "$LINENO" 5 ;;
|
||||
esac
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable the stacktrace feature" >&5
|
||||
$as_echo_n "checking whether to enable the stacktrace feature... " >&6; }
|
||||
|
||||
case "x$enable_stacktrace" in #(
|
||||
"xno") :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
QBT_REMOVE_CONFIG="$QBT_REMOVE_CONFIG stacktrace" ;; #(
|
||||
"xyes") :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG stacktrace" ;; #(
|
||||
"xauto") :
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
#include <execinfo.h>
|
||||
_ACEOF
|
||||
if ac_fn_cxx_try_compile "$LINENO"; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG stacktrace"
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
QBT_REMOVE_CONFIG="$QBT_REMOVE_CONFIG stacktrace"
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ;; #(
|
||||
*) :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_stacktrace" >&5
|
||||
$as_echo "$enable_stacktrace" >&6; }
|
||||
as_fn_error $? "Unknown option \"$enable_stacktrace\". Use either \"yes\" or \"no\"." "$LINENO" 5 ;;
|
||||
esac
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable the GUI" >&5
|
||||
$as_echo_n "checking whether to enable the GUI... " >&6; }
|
||||
case "x$enable_gui" in #(
|
||||
@@ -4679,6 +4636,7 @@ esac
|
||||
|
||||
|
||||
|
||||
|
||||
# Check whether --with-boost was given.
|
||||
if test "${with_boost+set}" = set; then :
|
||||
withval=$with_boost;
|
||||
@@ -6140,7 +6098,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by qbittorrent $as_me v4.1.1, which was
|
||||
This file was extended by qbittorrent $as_me v4.0.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -6198,7 +6156,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.1.1
|
||||
qbittorrent config.status v4.0.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -7455,7 +7413,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by qbittorrent $as_me v4.1.1, which was
|
||||
This file was extended by qbittorrent $as_me v4.0.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7513,7 +7471,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.1.1
|
||||
qbittorrent config.status v4.0.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
25
configure.ac
25
configure.ac
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.1.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.0.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_PROG_CC
|
||||
@@ -24,12 +24,6 @@ AC_ARG_ENABLE(debug,
|
||||
[],
|
||||
[enable_debug=no])
|
||||
|
||||
AC_ARG_ENABLE(stacktrace,
|
||||
[AS_HELP_STRING([--enable-stacktrace],
|
||||
[Enable stacktrace feature (default=auto)])],
|
||||
[],
|
||||
[enable_stacktrace=auto])
|
||||
|
||||
AC_ARG_ENABLE(gui,
|
||||
[AS_HELP_STRING([--disable-gui],
|
||||
[Disable the GUI for headless running. Disables QtDBus and the GeoIP Database.])],
|
||||
@@ -86,23 +80,6 @@ AS_CASE(["x$enable_debug"],
|
||||
[AC_MSG_RESULT([$enable_debug])
|
||||
AC_MSG_ERROR([Unknown option "$enable_debug". Use either "yes" or "no".])])
|
||||
|
||||
AC_MSG_CHECKING([whether to enable the stacktrace feature])
|
||||
AS_CASE(["x$enable_stacktrace"],
|
||||
["xno"],
|
||||
[AC_MSG_RESULT([no])
|
||||
QBT_REMOVE_CONFIG="$QBT_REMOVE_CONFIG stacktrace"],
|
||||
["xyes"],
|
||||
[AC_MSG_RESULT([yes])
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG stacktrace"],
|
||||
["xauto"],
|
||||
[AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#include <execinfo.h>]])],
|
||||
[AC_MSG_RESULT([yes])
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG stacktrace"],
|
||||
[AC_MSG_RESULT([no])
|
||||
QBT_REMOVE_CONFIG="$QBT_REMOVE_CONFIG stacktrace"])],
|
||||
[AC_MSG_RESULT([$enable_stacktrace])
|
||||
AC_MSG_ERROR([Unknown option "$enable_stacktrace". Use either "yes" or "no".])])
|
||||
|
||||
AC_MSG_CHECKING([whether to enable the GUI])
|
||||
AS_CASE(["x$enable_gui"],
|
||||
["xyes"],
|
||||
|
||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -45,7 +45,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.1.1</string>
|
||||
<string>4.0.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>qBit</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
||||
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -27,7 +27,7 @@ XPStyle on
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
; Program specific
|
||||
!define PROG_VERSION "4.1.1"
|
||||
!define PROG_VERSION "4.0.4"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
|
||||
@@ -17,7 +17,3 @@ tarball.commands += xz -f $${PROJECT_NAME}-$${PROJECT_VERSION}.tar &&
|
||||
tarball.commands += rm -fR $${PROJECT_NAME}-$${PROJECT_VERSION}
|
||||
|
||||
QMAKE_EXTRA_TARGETS += tarball
|
||||
|
||||
# For Qt Creator beautifier
|
||||
DISTFILES += \
|
||||
uncrustify.cfg
|
||||
|
||||
@@ -51,9 +51,9 @@ if (NOT WEBUI)
|
||||
add_definitions(-DDISABLE_WEBUI)
|
||||
endif (NOT WEBUI)
|
||||
|
||||
if (STACKTRACE)
|
||||
add_definitions(-DSTACKTRACE)
|
||||
endif(STACKTRACE)
|
||||
if (STACKTRACE_WIN)
|
||||
add_definitions(-DSTACKTRACE_WIN)
|
||||
endif(STACKTRACE_WIN)
|
||||
# nogui {
|
||||
# TARGET = qbittorrent-nox
|
||||
# } else {
|
||||
|
||||
@@ -53,16 +53,16 @@ if (WIN32)
|
||||
list(APPEND QBT_APP_SOURCES ../qbittorrent.exe.manifest)
|
||||
endif (WIN32)
|
||||
|
||||
if (STACKTRACE)
|
||||
if (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
||||
else (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
||||
if (GUI)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
||||
endif (GUI)
|
||||
endif (UNIX)
|
||||
endif (STACKTRACE)
|
||||
if (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
||||
endif (UNIX)
|
||||
|
||||
if (STACKTRACE_WIN)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
||||
if (GUI)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
||||
endif (GUI)
|
||||
endif (STACKTRACE_WIN)
|
||||
|
||||
# usesystemqtsingleapplication {
|
||||
# nogui {
|
||||
|
||||
@@ -25,16 +25,12 @@ SOURCES += \
|
||||
$$PWD/filelogger.cpp \
|
||||
$$PWD/main.cpp
|
||||
|
||||
stacktrace {
|
||||
unix {
|
||||
HEADERS += $$PWD/stacktrace.h
|
||||
}
|
||||
else {
|
||||
HEADERS += $$PWD/stacktrace_win.h
|
||||
!nogui {
|
||||
HEADERS += $$PWD/stacktrace_win_dlg.h
|
||||
FORMS += $$PWD/stacktrace_win_dlg.ui
|
||||
}
|
||||
unix: HEADERS += $$PWD/stacktrace.h
|
||||
strace_win {
|
||||
HEADERS += $$PWD/stacktrace_win.h
|
||||
!nogui {
|
||||
HEADERS += $$PWD/stacktrace_win_dlg.h
|
||||
FORMS += $$PWD/stacktrace_win_dlg.ui
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,73 +27,62 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "application.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QLibraryInfo>
|
||||
#include <QLocale>
|
||||
#include <QProcess>
|
||||
#include <QLibraryInfo>
|
||||
#include <QSysInfo>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/net/proxyconfigurationmanager.h"
|
||||
#include "base/net/smtp.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/rss/rss_autodownloader.h"
|
||||
#include "base/rss/rss_session.h"
|
||||
#include "base/scanfoldersmodel.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "filelogger.h"
|
||||
#include <QProcess>
|
||||
#include <QAtomicInt>
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include "gui/guiiconprovider.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QSessionManager>
|
||||
#include <windows.h>
|
||||
#include <QSharedMemory>
|
||||
#include <QSessionManager>
|
||||
#endif // Q_OS_WIN
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QFileOpenEvent>
|
||||
#include <QFont>
|
||||
#include <QUrl>
|
||||
#endif // Q_OS_MAC
|
||||
#include "addnewtorrentdialog.h"
|
||||
#include "gui/guiiconprovider.h"
|
||||
#include "mainwindow.h"
|
||||
#include "addnewtorrentdialog.h"
|
||||
#include "shutdownconfirmdlg.h"
|
||||
#else // DISABLE_GUI
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Shellapi.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "webui/webui.h"
|
||||
#endif
|
||||
|
||||
#include "application.h"
|
||||
#include "filelogger.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/scanfoldersmodel.h"
|
||||
#include "base/net/smtp.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/net/proxyconfigurationmanager.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/rss/rss_autodownloader.h"
|
||||
#include "base/rss/rss_session.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
#define SETTINGS_KEY(name) "Application/" name
|
||||
|
||||
// FileLogger properties keys
|
||||
#define FILELOGGER_SETTINGS_KEY(name) QStringLiteral(SETTINGS_KEY("FileLogger/") name)
|
||||
#define FILELOGGER_SETTINGS_KEY(name) SETTINGS_KEY("FileLogger/") name
|
||||
const QString KEY_FILELOGGER_ENABLED = FILELOGGER_SETTINGS_KEY("Enabled");
|
||||
const QString KEY_FILELOGGER_PATH = FILELOGGER_SETTINGS_KEY("Path");
|
||||
const QString KEY_FILELOGGER_BACKUP = FILELOGGER_SETTINGS_KEY("Backup");
|
||||
@@ -151,11 +140,11 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
|
||||
connect(this, &QGuiApplication::commitDataRequest, this, &Application::shutdownCleanup, Qt::DirectConnection);
|
||||
connect(this, SIGNAL(commitDataRequest(QSessionManager &)), this, SLOT(shutdownCleanup(QSessionManager &)), Qt::DirectConnection);
|
||||
#endif
|
||||
|
||||
connect(this, &Application::messageReceived, this, &Application::processMessage);
|
||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
|
||||
connect(this, SIGNAL(messageReceived(const QString &)), SLOT(processMessage(const QString &)));
|
||||
connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
|
||||
|
||||
if (isFileLoggerEnabled())
|
||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||
@@ -280,58 +269,26 @@ void Application::processMessage(const QString &message)
|
||||
m_paramsQueue.append(params);
|
||||
}
|
||||
|
||||
void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) const
|
||||
void Application::runExternalProgram(BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
|
||||
QString program = Preferences::instance()->getAutoRunProgram();
|
||||
program.replace("%N", torrent->name());
|
||||
program.replace("%L", torrent->category());
|
||||
|
||||
QStringList tags = torrent->tags().toList();
|
||||
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||
program.replace("%G", tags.join(','));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const auto chopPathSep = [](const QString &str) -> QString
|
||||
{
|
||||
if (str.endsWith('\\'))
|
||||
return str.mid(0, (str.length() -1));
|
||||
return str;
|
||||
};
|
||||
program.replace("%F", chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
|
||||
program.replace("%R", chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
|
||||
program.replace("%D", chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
|
||||
#else
|
||||
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
|
||||
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
||||
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
||||
#endif
|
||||
program.replace("%C", QString::number(torrent->filesCount()));
|
||||
program.replace("%Z", QString::number(torrent->totalSize()));
|
||||
program.replace("%T", torrent->currentTracker());
|
||||
program.replace("%I", torrent->hash());
|
||||
|
||||
Logger *logger = Logger::instance();
|
||||
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
||||
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name()).arg(program));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
std::unique_ptr<wchar_t[]> programWchar(new wchar_t[program.length() + 1] {});
|
||||
program.toWCharArray(programWchar.get());
|
||||
|
||||
// Need to split arguments manually because QProcess::startDetached(QString)
|
||||
// will strip off empty parameters.
|
||||
// E.g. `python.exe "1" "" "3"` will become `python.exe "1" "3"`
|
||||
int argCount = 0;
|
||||
LPWSTR *args = ::CommandLineToArgvW(programWchar.get(), &argCount);
|
||||
|
||||
QStringList argList;
|
||||
for (int i = 1; i < argCount; ++i)
|
||||
argList += QString::fromWCharArray(args[i]);
|
||||
|
||||
QProcess::startDetached(QString::fromWCharArray(args[0]), argList);
|
||||
|
||||
::LocalFree(args);
|
||||
#else
|
||||
#if defined(Q_OS_UNIX)
|
||||
QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program});
|
||||
#else
|
||||
QProcess::startDetached(program);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -444,7 +401,7 @@ void Application::processParams(const QStringList ¶ms)
|
||||
}
|
||||
|
||||
if (param.startsWith(QLatin1String("@addPaused="))) {
|
||||
torrentParams.addPaused = param.midRef(11).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
torrentParams.addPaused = param.mid(11).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -469,7 +426,7 @@ void Application::processParams(const QStringList ¶ms)
|
||||
}
|
||||
|
||||
if (param.startsWith(QLatin1String("@skipDialog="))) {
|
||||
skipTorrentDialog = param.midRef(12).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
skipTorrentDialog = param.mid(12).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -501,8 +458,8 @@ int Application::exec(const QStringList ¶ms)
|
||||
#endif
|
||||
|
||||
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);
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle *const)), SLOT(torrentFinished(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(allTorrentsFinished()), SLOT(allTorrentsFinished()), Qt::QueuedConnection);
|
||||
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
Net::GeoIPManager::initInstance();
|
||||
@@ -525,16 +482,13 @@ int Application::exec(const QStringList ¶ms)
|
||||
#ifndef DISABLE_WEBUI
|
||||
Preferences* const pref = Preferences::instance();
|
||||
// Display some information to the user
|
||||
const QString mesg = QString("\n******** %1 ********\n").arg(tr("Information"))
|
||||
+ tr("To control qBittorrent, access the Web UI at %1")
|
||||
.arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n'
|
||||
+ tr("The Web UI administrator user name is: %1").arg(pref->getWebUiUsername()) + '\n';
|
||||
printf("%s", qUtf8Printable(mesg));
|
||||
std::cout << std::endl << "******** " << qPrintable(tr("Information")) << " ********" << std::endl;
|
||||
std::cout << qPrintable(tr("To control qBittorrent, access the Web UI at http://localhost:%1").arg(QString::number(pref->getWebUiPort()))) << std::endl;
|
||||
std::cout << qPrintable(tr("The Web UI administrator user name is: %1").arg(pref->getWebUiUsername())) << std::endl;
|
||||
qDebug() << "Password:" << pref->getWebUiPassword();
|
||||
if (pref->getWebUiPassword() == "f6fdffe48c908deb0f4c3bd36c032e72") {
|
||||
const QString warning = 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';
|
||||
printf("%s", qUtf8Printable(warning));
|
||||
std::cout << qPrintable(tr("The Web UI administrator password is still the default one: %1").arg("adminadmin")) << std::endl;
|
||||
std::cout << qPrintable(tr("This is a security risk, please consider changing your password from program preferences.")) << std::endl;
|
||||
}
|
||||
#endif // DISABLE_WEBUI
|
||||
#else
|
||||
@@ -672,7 +626,7 @@ void Application::shutdownCleanup(QSessionManager &manager)
|
||||
// According to the qt docs we shouldn't call quit() inside a slot.
|
||||
// aboutToQuit() is never emitted if the user hits "Cancel" in
|
||||
// the above dialog.
|
||||
QTimer::singleShot(0, qApp, &QCoreApplication::quit);
|
||||
QTimer::singleShot(0, qApp, SLOT(quit()));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -693,7 +647,7 @@ void Application::cleanup()
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
typedef BOOL (WINAPI *PSHUTDOWNBRCREATE)(HWND, LPCWSTR);
|
||||
const auto shutdownBRCreate = Utils::Misc::loadWinAPI<PSHUTDOWNBRCREATE>("User32.dll", "ShutdownBlockReasonCreate");
|
||||
PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)::GetProcAddress(::GetModuleHandleW(L"User32.dll"), "ShutdownBlockReasonCreate");
|
||||
// Only available on Vista+
|
||||
if (shutdownBRCreate)
|
||||
shutdownBRCreate((HWND)m_window->effectiveWinId(), tr("Saving torrent progress...").toStdWString().c_str());
|
||||
@@ -735,7 +689,7 @@ void Application::cleanup()
|
||||
if (m_window) {
|
||||
#ifdef Q_OS_WIN
|
||||
typedef BOOL (WINAPI *PSHUTDOWNBRDESTROY)(HWND);
|
||||
const auto shutdownBRDestroy = Utils::Misc::loadWinAPI<PSHUTDOWNBRDESTROY>("User32.dll", "ShutdownBlockReasonDestroy");
|
||||
PSHUTDOWNBRDESTROY shutdownBRDestroy = (PSHUTDOWNBRDESTROY)::GetProcAddress(::GetModuleHandleW(L"User32.dll"), "ShutdownBlockReasonDestroy");
|
||||
// Only available on Vista+
|
||||
if (shutdownBRDestroy)
|
||||
shutdownBRDestroy((HWND)m_window->effectiveWinId());
|
||||
|
||||
@@ -110,9 +110,9 @@ public:
|
||||
protected:
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_MAC
|
||||
bool event(QEvent *) override;
|
||||
bool event(QEvent *);
|
||||
#endif
|
||||
bool notify(QObject* receiver, QEvent* event) override;
|
||||
bool notify(QObject* receiver, QEvent* event);
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
@@ -146,7 +146,7 @@ private:
|
||||
|
||||
void initializeTranslation();
|
||||
void processParams(const QStringList ¶ms);
|
||||
void runExternalProgram(const BitTorrent::TorrentHandle *torrent) const;
|
||||
void runExternalProgram(BitTorrent::TorrentHandle *const torrent) const;
|
||||
void sendNotificationEmail(const BitTorrent::TorrentHandle *torrent);
|
||||
void validateCommandLineParameters();
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include "cmdoptions.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
@@ -216,7 +216,7 @@ namespace
|
||||
int res = val.toInt(&ok);
|
||||
if (!ok) {
|
||||
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
|
||||
.arg(envVarName(), val);
|
||||
.arg(envVarName()).arg(val);
|
||||
return defaultValue;
|
||||
}
|
||||
return res;
|
||||
@@ -293,7 +293,7 @@ namespace
|
||||
}
|
||||
else {
|
||||
qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
|
||||
.arg(QLatin1String("true|false"), envVarName(), val);
|
||||
.arg(QLatin1String("true|false")).arg(envVarName()).arg(val);
|
||||
return TriStateBool::Undefined;
|
||||
}
|
||||
}
|
||||
@@ -578,7 +578,7 @@ QString makeUsage(const QString &prgName)
|
||||
void displayUsage(const QString &prgName)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
printf("%s\n", qUtf8Printable(makeUsage(prgName)));
|
||||
std::cout << qPrintable(makeUsage(prgName)) << std::endl;
|
||||
#else
|
||||
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
|
||||
msgBox.show(); // Need to be shown or to moveToCenter does not work
|
||||
|
||||
@@ -41,7 +41,7 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
|
||||
{
|
||||
m_flusher.setInterval(0);
|
||||
m_flusher.setSingleShot(true);
|
||||
connect(&m_flusher, &QTimer::timeout, this, &FileLogger::flushLog);
|
||||
connect(&m_flusher, SIGNAL(timeout()), SLOT(flushLog()));
|
||||
|
||||
changePath(path);
|
||||
if (deleteOld)
|
||||
@@ -51,7 +51,7 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
|
||||
foreach (const Log::Msg& msg, logger->getMessages())
|
||||
addLogMessage(msg);
|
||||
|
||||
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
|
||||
connect(logger, SIGNAL(newLogMessage(const Log::Msg &)), SLOT(addLogMessage(const Log::Msg &)));
|
||||
}
|
||||
|
||||
FileLogger::~FileLogger()
|
||||
@@ -83,21 +83,21 @@ void FileLogger::changePath(const QString& newPath)
|
||||
void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
||||
{
|
||||
QDateTime date = QDateTime::currentDateTime();
|
||||
QDir dir(Utils::Fs::branchPath(m_path));
|
||||
QDir dir(m_path);
|
||||
|
||||
switch (ageType) {
|
||||
case DAYS:
|
||||
date = date.addDays(age);
|
||||
break;
|
||||
case MONTHS:
|
||||
date = date.addMonths(age);
|
||||
break;
|
||||
default:
|
||||
date = date.addYears(age);
|
||||
}
|
||||
|
||||
foreach (const QFileInfo file, dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed)) {
|
||||
QDateTime modificationDate = file.lastModified();
|
||||
switch (ageType) {
|
||||
case DAYS:
|
||||
modificationDate = modificationDate.addDays(age);
|
||||
break;
|
||||
case MONTHS:
|
||||
modificationDate = modificationDate.addMonths(age);
|
||||
break;
|
||||
default:
|
||||
modificationDate = modificationDate.addYears(age);
|
||||
}
|
||||
if (modificationDate > date)
|
||||
if (file.lastModified() < date)
|
||||
break;
|
||||
Utils::Fs::forceRemove(file.absoluteFilePath());
|
||||
}
|
||||
@@ -165,7 +165,7 @@ void FileLogger::openLogFile()
|
||||
|| !m_logFile->setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
|
||||
delete m_logFile;
|
||||
m_logFile = nullptr;
|
||||
Logger::instance()->addMessage(tr("An error occurred while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
|
||||
Logger::instance()->addMessage(tr("An error occured while trying to open the log file. Logging to file is disabled."), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QScopedPointer>
|
||||
#include <QThread>
|
||||
@@ -57,28 +55,33 @@ Q_IMPORT_PLUGIN(QICOPlugin)
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#include <signal.h>
|
||||
#ifdef STACKTRACE
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <signal.h>
|
||||
#include <execinfo.h>
|
||||
#include "stacktrace.h"
|
||||
#else
|
||||
#endif // Q_OS_UNIX
|
||||
|
||||
#ifdef STACKTRACE_WIN
|
||||
#include <signal.h>
|
||||
#include "stacktrace_win.h"
|
||||
#include "stacktrace_win_dlg.h"
|
||||
#endif // Q_OS_UNIX
|
||||
#endif //STACKTRACE
|
||||
#endif //STACKTRACE_WIN
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#include "application.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/preferences.h"
|
||||
#include "cmdoptions.h"
|
||||
|
||||
#include "upgrade.h"
|
||||
|
||||
// Signal handlers
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
void sigNormalHandler(int signum);
|
||||
#ifdef STACKTRACE
|
||||
void sigAbnormalHandler(int signum);
|
||||
#endif
|
||||
// sys_signame[] is only defined in BSD
|
||||
const char *sysSigName[] = {
|
||||
#if defined(Q_OS_WIN)
|
||||
@@ -93,6 +96,7 @@ const char *sysSigName[] = {
|
||||
"SIGPWR", "SIGUNUSED"
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
void reportToUser(const char* str);
|
||||
@@ -165,7 +169,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Set environment variable
|
||||
if (!qputenv("QBITTORRENT", QBT_VERSION))
|
||||
fprintf(stderr, "Couldn't set environment variable...\n");
|
||||
std::cerr << "Couldn't set environment variable...\n";
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (!userAgreesWithLegalNotice())
|
||||
@@ -253,9 +257,9 @@ int main(int argc, char *argv[])
|
||||
showSplashScreen();
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
signal(SIGINT, sigNormalHandler);
|
||||
signal(SIGTERM, sigNormalHandler);
|
||||
#ifdef STACKTRACE
|
||||
signal(SIGABRT, sigAbnormalHandler);
|
||||
signal(SIGSEGV, sigAbnormalHandler);
|
||||
#endif
|
||||
@@ -279,6 +283,7 @@ void reportToUser(const char* str)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
void sigNormalHandler(int signum)
|
||||
{
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
@@ -292,7 +297,6 @@ void sigNormalHandler(int signum)
|
||||
qApp->exit(); // unsafe, but exit anyway
|
||||
}
|
||||
|
||||
#ifdef STACKTRACE
|
||||
void sigAbnormalHandler(int signum)
|
||||
{
|
||||
const char *sigName = sysSigName[signum];
|
||||
@@ -305,18 +309,16 @@ void sigAbnormalHandler(int signum)
|
||||
reportToUser(sigName);
|
||||
reportToUser("\n");
|
||||
print_stacktrace(); // unsafe
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
#endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
#ifdef STACKTRACE_WIN
|
||||
StraceDlg dlg; // unsafe
|
||||
dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
|
||||
dlg.exec();
|
||||
#endif
|
||||
|
||||
#endif // STACKTRACE_WIN
|
||||
signal(signum, SIG_DFL);
|
||||
raise(signum);
|
||||
}
|
||||
#endif // STACKTRACE
|
||||
#endif // defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||
|
||||
#if !defined(DISABLE_GUI)
|
||||
void showSplashScreen()
|
||||
@@ -329,14 +331,14 @@ void showSplashScreen()
|
||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
||||
QSplashScreen *splash = new QSplashScreen(splash_img);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
||||
QTimer::singleShot(1500, splash, SLOT(deleteLater()));
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
void setupDpi()
|
||||
{
|
||||
if (qEnvironmentVariableIsEmpty("QT_AUTO_SCREEN_SCALE_FACTOR"))
|
||||
if (qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR").isEmpty())
|
||||
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
@@ -344,7 +346,7 @@ void setupDpi()
|
||||
|
||||
void displayVersion()
|
||||
{
|
||||
printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
|
||||
std::cout << qPrintable(qApp->applicationName()) << " " << QBT_VERSION << std::endl;
|
||||
}
|
||||
|
||||
void displayBadArgMessage(const QString& message)
|
||||
@@ -357,10 +359,9 @@ void displayBadArgMessage(const QString& message)
|
||||
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
||||
msgBox.exec();
|
||||
#else
|
||||
const QString errMsg = QObject::tr("Bad command line: ") + '\n'
|
||||
+ message + '\n'
|
||||
+ help + '\n';
|
||||
fprintf(stderr, "%s", qUtf8Printable(errMsg));
|
||||
std::cerr << qPrintable(QObject::tr("Bad command line: "));
|
||||
std::cerr << qPrintable(message) << std::endl;
|
||||
std::cerr << qPrintable(help) << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -371,12 +372,9 @@ bool userAgreesWithLegalNotice()
|
||||
return true;
|
||||
|
||||
#ifdef DISABLE_GUI
|
||||
const QString eula = QString("\n*** %1 ***\n").arg(QObject::tr("Legal Notice"))
|
||||
+ QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + "\n\n"
|
||||
+ QObject::tr("No further notices will be issued.") + "\n\n"
|
||||
+ QObject::tr("Press %1 key to accept and continue...").arg("'y'") + '\n';
|
||||
printf("%s", qUtf8Printable(eula));
|
||||
|
||||
std::cout << std::endl << "*** " << qPrintable(QObject::tr("Legal Notice")) << " ***" << std::endl;
|
||||
std::cout << qPrintable(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued.")) << std::endl << std::endl;
|
||||
std::cout << qPrintable(QObject::tr("Press %1 key to accept and continue...").arg("'y'")) << std::endl;
|
||||
char ret = getchar(); // Read pressed key
|
||||
if (ret == 'y' || ret == 'Y') {
|
||||
// Save the answer
|
||||
|
||||
@@ -138,7 +138,7 @@ bool straceWin::makeRelativePath(const QString& dir, QString& file)
|
||||
|
||||
QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
||||
{
|
||||
IMAGEHLP_LINE64 line {};
|
||||
IMAGEHLP_LINE64 line = {0};
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
DWORD dwDisplacement = 0;
|
||||
|
||||
@@ -291,7 +291,7 @@ const QString straceWin::getBacktrace()
|
||||
demangle(funcName);
|
||||
#endif
|
||||
|
||||
// now ihsf.InstructionOffset points to the instruction that follows CALL instruction
|
||||
// now ihsf.InstructionOffset points to the instruction that follows CALL instuction
|
||||
// decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
|
||||
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
|
||||
}
|
||||
|
||||
@@ -61,11 +61,10 @@
|
||||
bool userAcceptsUpgrade()
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
printf("\n*** %s ***\n", qUtf8Printable(QObject::tr("Upgrade")));
|
||||
std::cout << std::endl << "*** " << qPrintable(QObject::tr("Upgrade")) << " ***" << std::endl;
|
||||
char ret = '\0';
|
||||
do {
|
||||
printf("%s\n"
|
||||
, qUtf8Printable(QObject::tr("You updated from an older version that saved things differently. You must migrate to the new saving system. You will not be able to use an older version than v3.3.0 again. Continue? [y/n]")));
|
||||
std::cout << qPrintable(QObject::tr("You updated from an older version that saved things differently. You must migrate to the new saving system. You will not be able to use an older version than v3.3.0 again. Continue? [y/n]")) << std::endl;
|
||||
ret = getchar(); // Read pressed key
|
||||
}
|
||||
while ((ret != 'y') && (ret != 'Y') && (ret != 'n') && (ret != 'N'));
|
||||
@@ -114,27 +113,12 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
|
||||
bool v3_3 = false;
|
||||
int queuePosition = 0;
|
||||
QString outFilePath = filepath;
|
||||
QRegExp rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(.+)$"));
|
||||
QRegExp rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(\\d+)$"));
|
||||
if (rx.indexIn(filepath) != -1) {
|
||||
// Old v3.3.x format had a number at the end indicating the queue position.
|
||||
// The naming scheme was '<infohash>.fastresume.<queueposition>'.
|
||||
// However, QSaveFile, which uses QTemporaryFile internally, might leave
|
||||
// non-commited files behind eg after a crash. These files have the
|
||||
// naming scheme '<infohash>.fastresume.XXXXXX' where each X is a random
|
||||
// character. So we detect if the last part is present. Then check if it
|
||||
// is 6 chars long. If all the 6 chars are digits we assume it is an old
|
||||
// v3.3.x format. Otherwise it is considered a non-commited fastresume
|
||||
// and is deleted, because it may be a corrupted/incomplete fastresume.
|
||||
// NOTE: When the upgrade code is removed, we must continue to perform
|
||||
// cleanup of non-commited QSaveFile/QTemporaryFile fastresumes
|
||||
// old v3.3.x format
|
||||
queuePosition = rx.cap(2).toInt();
|
||||
if ((rx.cap(2).size() == 6) && (queuePosition <= 99999)) {
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
v3_3 = true;
|
||||
outFilePath.replace(QRegExp("\\.fastresume\\..+$"), ".fastresume");
|
||||
outFilePath.replace(QRegExp("\\.\\d+$"), "");
|
||||
}
|
||||
else {
|
||||
queuePosition = fastOld.dict_find_int_value("qBt-queuePosition", 0);
|
||||
@@ -145,15 +129,6 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
|
||||
// in versions < 3.3 we have -1 for seeding torrents, so we convert it to 0
|
||||
fastNew["qBt-queuePosition"] = (queuePosition >= 0 ? queuePosition : 0);
|
||||
|
||||
if (v3_3) {
|
||||
QFileInfo oldFile(filepath);
|
||||
QFileInfo newFile(outFilePath);
|
||||
if (newFile.exists()
|
||||
&& (oldFile.lastModified() < newFile.lastModified())) {
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
QFile file2(outFilePath);
|
||||
QVector<char> out;
|
||||
libtorrent::bencode(std::back_inserter(out), fastNew);
|
||||
@@ -211,10 +186,9 @@ bool upgrade(bool ask = true)
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = oldResumeData.cbegin(); i != oldResumeData.cend(); ++i) {
|
||||
const QVariantHash oldTorrent = i.value().toHash();
|
||||
foreach (const QString &hash, oldResumeData.keys()) {
|
||||
QVariantHash oldTorrent = oldResumeData[hash].toHash();
|
||||
if (oldTorrent.value("is_magnet", false).toBool()) {
|
||||
const QString &hash = i.key();
|
||||
libtorrent::entry resumeData;
|
||||
resumeData["qBt-magnetUri"] = oldTorrent.value("magnet_uri").toString().toStdString();
|
||||
resumeData["qBt-paused"] = false;
|
||||
|
||||
@@ -19,7 +19,6 @@ bittorrent/torrentinfo.h
|
||||
bittorrent/tracker.h
|
||||
bittorrent/trackerentry.h
|
||||
http/connection.h
|
||||
http/httperror.h
|
||||
http/irequesthandler.h
|
||||
http/requestparser.h
|
||||
http/responsebuilder.h
|
||||
@@ -44,10 +43,6 @@ rss/rss_feed.h
|
||||
rss/rss_folder.h
|
||||
rss/rss_item.h
|
||||
rss/rss_session.h
|
||||
search/searchdownloadhandler.h
|
||||
search/searchhandler.h
|
||||
search/searchpluginmanager.h
|
||||
utils/bytearray.h
|
||||
utils/fs.h
|
||||
utils/gzip.h
|
||||
utils/misc.h
|
||||
@@ -55,9 +50,7 @@ utils/net.h
|
||||
utils/random.h
|
||||
utils/string.h
|
||||
utils/version.h
|
||||
algorithm.h
|
||||
asyncfilestorage.h
|
||||
exceptions.h
|
||||
filesystemwatcher.h
|
||||
global.h
|
||||
iconprovider.h
|
||||
@@ -66,6 +59,7 @@ logger.h
|
||||
preferences.h
|
||||
profile.h
|
||||
scanfoldersmodel.h
|
||||
searchengine.h
|
||||
settingsstorage.h
|
||||
torrentfileguard.h
|
||||
torrentfilter.h
|
||||
@@ -90,7 +84,6 @@ bittorrent/torrentinfo.cpp
|
||||
bittorrent/tracker.cpp
|
||||
bittorrent/trackerentry.cpp
|
||||
http/connection.cpp
|
||||
http/httperror.cpp
|
||||
http/requestparser.cpp
|
||||
http/responsebuilder.cpp
|
||||
http/responsegenerator.cpp
|
||||
@@ -113,10 +106,6 @@ rss/rss_feed.cpp
|
||||
rss/rss_folder.cpp
|
||||
rss/rss_item.cpp
|
||||
rss/rss_session.cpp
|
||||
search/searchdownloadhandler.cpp
|
||||
search/searchhandler.cpp
|
||||
search/searchpluginmanager.cpp
|
||||
utils/bytearray.cpp
|
||||
utils/fs.cpp
|
||||
utils/gzip.cpp
|
||||
utils/misc.cpp
|
||||
@@ -124,13 +113,13 @@ utils/net.cpp
|
||||
utils/random.cpp
|
||||
utils/string.cpp
|
||||
asyncfilestorage.cpp
|
||||
exceptions.cpp
|
||||
filesystemwatcher.cpp
|
||||
iconprovider.cpp
|
||||
logger.cpp
|
||||
preferences.cpp
|
||||
profile.cpp
|
||||
scanfoldersmodel.cpp
|
||||
searchengine.cpp
|
||||
settingsstorage.cpp
|
||||
torrentfileguard.cpp
|
||||
torrentfilter.cpp
|
||||
|
||||
@@ -34,14 +34,14 @@
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
class AsyncFileStorageError : public std::runtime_error
|
||||
class AsyncFileStorageError: public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit AsyncFileStorageError(const QString &message);
|
||||
QString message() const;
|
||||
};
|
||||
|
||||
class AsyncFileStorage : public QObject
|
||||
class AsyncFileStorage: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
HEADERS += \
|
||||
$$PWD/algorithm.h \
|
||||
$$PWD/asyncfilestorage.h \
|
||||
$$PWD/bittorrent/addtorrentparams.h \
|
||||
$$PWD/bittorrent/cachestatus.h \
|
||||
@@ -18,11 +17,9 @@ HEADERS += \
|
||||
$$PWD/bittorrent/torrentinfo.h \
|
||||
$$PWD/bittorrent/tracker.h \
|
||||
$$PWD/bittorrent/trackerentry.h \
|
||||
$$PWD/exceptions.h \
|
||||
$$PWD/filesystemwatcher.h \
|
||||
$$PWD/global.h \
|
||||
$$PWD/http/connection.h \
|
||||
$$PWD/http/httperror.h \
|
||||
$$PWD/http/irequesthandler.h \
|
||||
$$PWD/http/requestparser.h \
|
||||
$$PWD/http/responsebuilder.h \
|
||||
@@ -53,9 +50,7 @@ HEADERS += \
|
||||
$$PWD/rss/rss_item.h \
|
||||
$$PWD/rss/rss_session.h \
|
||||
$$PWD/scanfoldersmodel.h \
|
||||
$$PWD/search/searchhandler.h \
|
||||
$$PWD/search/searchdownloadhandler.h \
|
||||
$$PWD/search/searchpluginmanager.h \
|
||||
$$PWD/searchengine.h \
|
||||
$$PWD/settingsstorage.h \
|
||||
$$PWD/settingvalue.h \
|
||||
$$PWD/torrentfileguard.h \
|
||||
@@ -63,7 +58,6 @@ HEADERS += \
|
||||
$$PWD/tristatebool.h \
|
||||
$$PWD/types.h \
|
||||
$$PWD/unicodestrings.h \
|
||||
$$PWD/utils/bytearray.h \
|
||||
$$PWD/utils/fs.h \
|
||||
$$PWD/utils/gzip.h \
|
||||
$$PWD/utils/misc.h \
|
||||
@@ -88,10 +82,8 @@ SOURCES += \
|
||||
$$PWD/bittorrent/torrentinfo.cpp \
|
||||
$$PWD/bittorrent/tracker.cpp \
|
||||
$$PWD/bittorrent/trackerentry.cpp \
|
||||
$$PWD/exceptions.cpp \
|
||||
$$PWD/filesystemwatcher.cpp \
|
||||
$$PWD/http/connection.cpp \
|
||||
$$PWD/http/httperror.cpp \
|
||||
$$PWD/http/requestparser.cpp \
|
||||
$$PWD/http/responsebuilder.cpp \
|
||||
$$PWD/http/responsegenerator.cpp \
|
||||
@@ -119,14 +111,11 @@ SOURCES += \
|
||||
$$PWD/rss/rss_item.cpp \
|
||||
$$PWD/rss/rss_session.cpp \
|
||||
$$PWD/scanfoldersmodel.cpp \
|
||||
$$PWD/search/searchdownloadhandler.cpp \
|
||||
$$PWD/search/searchhandler.cpp \
|
||||
$$PWD/search/searchpluginmanager.cpp \
|
||||
$$PWD/searchengine.cpp \
|
||||
$$PWD/settingsstorage.cpp \
|
||||
$$PWD/torrentfileguard.cpp \
|
||||
$$PWD/torrentfilter.cpp \
|
||||
$$PWD/tristatebool.cpp \
|
||||
$$PWD/utils/bytearray.cpp \
|
||||
$$PWD/utils/fs.cpp \
|
||||
$$PWD/utils/gzip.cpp \
|
||||
$$PWD/utils/misc.cpp \
|
||||
|
||||
@@ -91,7 +91,7 @@ bool InfoHash::operator!=(const InfoHash &other) const
|
||||
return (m_nativeHash != other.m_nativeHash);
|
||||
}
|
||||
|
||||
uint BitTorrent::qHash(const InfoHash &key, uint seed)
|
||||
uint qHash(const InfoHash &key, uint seed)
|
||||
{
|
||||
return qHash(static_cast<QString>(key), seed);
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace BitTorrent
|
||||
libtorrent::sha1_hash m_nativeHash;
|
||||
QString m_hashString;
|
||||
};
|
||||
|
||||
uint qHash(const InfoHash &key, uint seed);
|
||||
}
|
||||
|
||||
uint qHash(const BitTorrent::InfoHash &key, uint seed);
|
||||
|
||||
#endif // BITTORRENT_INFOHASH_H
|
||||
|
||||
@@ -55,7 +55,7 @@ void BandwidthScheduler::start()
|
||||
|
||||
bool BandwidthScheduler::isTimeForAlternative() const
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
|
||||
QTime start = pref->getSchedulerStartTime();
|
||||
QTime end = pref->getSchedulerEndTime();
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class BandwidthScheduler : public QObject
|
||||
class BandwidthScheduler: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(BandwidthScheduler)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Bittorrent Client using Qt and libt.
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -147,13 +147,13 @@ int FilterParserThread::parseDATFilterFile()
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
int dataSize = bytesRead + offset;
|
||||
if ((bytesRead == 0) && (dataSize == 0))
|
||||
if (bytesRead == 0 && dataSize == 0)
|
||||
break;
|
||||
|
||||
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))) {
|
||||
if (!(bytesRead == 0 && dataSize > 0)) {
|
||||
for (int i = start; i < dataSize; ++i) {
|
||||
if (buffer[i] == '\n') {
|
||||
endOfLine = i;
|
||||
@@ -295,13 +295,13 @@ int FilterParserThread::parseP2PFilterFile()
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
int dataSize = bytesRead + offset;
|
||||
if ((bytesRead == 0) && (dataSize == 0))
|
||||
if (bytesRead == 0 && dataSize == 0)
|
||||
break;
|
||||
|
||||
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))) {
|
||||
if (!(bytesRead == 0 && dataSize > 0)) {
|
||||
for (int i = start; i < dataSize; ++i) {
|
||||
if (buffer[i] == '\n') {
|
||||
endOfLine = i;
|
||||
@@ -610,7 +610,7 @@ int FilterParserThread::findAndNullDelimiter(char *const data, char delimiter, i
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FilterParserThread::trim(char *const data, int start, int end)
|
||||
int FilterParserThread::trim(char* const data, int start, int end)
|
||||
{
|
||||
if (start >= end) return start;
|
||||
int newStart = start;
|
||||
|
||||
@@ -49,7 +49,7 @@ void ResumeDataSavingManager::saveResumeData(QString infoHash, QByteArray data)
|
||||
resumeFile.write(data);
|
||||
if (!resumeFile.commit()) {
|
||||
Logger::instance()->addMessage(QString("Couldn't save resume data in %1. Error: %2")
|
||||
.arg(filepath, resumeFile.errorString()), Log::WARNING);
|
||||
.arg(filepath).arg(resumeFile.errorString()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ Statistics::Statistics(Session *session)
|
||||
, m_dirty(false)
|
||||
{
|
||||
load();
|
||||
connect(&m_timer, &QTimer::timeout, this, &Statistics::gather);
|
||||
connect(&m_timer, SIGNAL(timeout()), this, SLOT(gather()));
|
||||
m_timer.start(60 * 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace BitTorrent
|
||||
class Session;
|
||||
}
|
||||
|
||||
class Statistics : public QObject
|
||||
class Statistics : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Statistics)
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
#include "session.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
@@ -49,6 +49,9 @@
|
||||
#include <QUuid>
|
||||
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#endif
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/disk_io_thread.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
@@ -57,19 +60,17 @@
|
||||
#include <libtorrent/extensions/smart_ban.hpp>
|
||||
#include <libtorrent/identify_client.hpp>
|
||||
#include <libtorrent/ip_filter.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
#include <libtorrent/lazy_entry.hpp>
|
||||
#endif
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#include <libtorrent/session.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/session_stats.hpp>
|
||||
#endif
|
||||
#include <libtorrent/session_status.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
#include <libtorrent/lazy_entry.hpp>
|
||||
#else
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#include <libtorrent/session_stats.hpp>
|
||||
#endif
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadhandler.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
@@ -94,7 +95,6 @@
|
||||
#include "trackerentry.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <wincrypt.h>
|
||||
#include <iphlpapi.h>
|
||||
#endif
|
||||
|
||||
@@ -123,19 +123,21 @@ namespace
|
||||
QString convertIfaceNameToGuid(const QString &name);
|
||||
#endif
|
||||
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
|
||||
QStringMap map_cast(const QVariantMap &map)
|
||||
{
|
||||
QStringMap result;
|
||||
for (auto i = map.cbegin(); i != map.cend(); ++i)
|
||||
result[i.key()] = i.value().toString();
|
||||
foreach (const QString &key, map.keys())
|
||||
result[key] = map.value(key).toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap map_cast(const QStringMap &map)
|
||||
{
|
||||
QVariantMap result;
|
||||
for (auto i = map.cbegin(); i != map.cend(); ++i)
|
||||
result[i.key()] = i.value();
|
||||
foreach (const QString &key, map.keys())
|
||||
result[key] = map.value(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -197,8 +199,7 @@ namespace
|
||||
{
|
||||
QStringMap expanded = categories;
|
||||
|
||||
for (auto i = categories.cbegin(); i != categories.cend(); ++i) {
|
||||
const QString &category = i.key();
|
||||
foreach (const QString &category, categories.keys()) {
|
||||
foreach (const QString &subcat, Session::expandCategory(category)) {
|
||||
if (!expanded.contains(subcat))
|
||||
expanded[subcat] = "";
|
||||
@@ -279,11 +280,6 @@ Session::Session(QObject *parent)
|
||||
, m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
|
||||
, m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
|
||||
, m_guidedReadCacheEnabled(BITTORRENT_SESSION_KEY("GuidedReadCache"), true)
|
||||
#ifdef Q_OS_WIN
|
||||
, m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), true)
|
||||
#else
|
||||
, m_coalesceReadWriteEnabled(BITTORRENT_SESSION_KEY("CoalesceReadWrite"), false)
|
||||
#endif
|
||||
, m_isSuggestMode(BITTORRENT_SESSION_KEY("SuggestMode"), false)
|
||||
, m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
|
||||
, m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY("SendBufferLowWatermark"), 10)
|
||||
@@ -294,9 +290,6 @@ Session::Session(QObject *parent)
|
||||
, m_maxActiveUploads(BITTORRENT_SESSION_KEY("MaxActiveUploads"), 3, lowerLimited(-1))
|
||||
, m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
|
||||
, m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY("IgnoreSlowTorrentsForQueueing"), false)
|
||||
, m_downloadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsDownloadRate"), 2)
|
||||
, m_uploadRateForSlowTorrents(BITTORRENT_SESSION_KEY("SlowTorrentsUploadRate"), 2)
|
||||
, m_slowTorrentsInactivityTimer(BITTORRENT_SESSION_KEY("SlowTorrentsInactivityTimer"), 60)
|
||||
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), true)
|
||||
@@ -370,16 +363,11 @@ Session::Session(QObject *parent)
|
||||
, m_numResumeData(0)
|
||||
, m_extraLimit(0)
|
||||
, m_useProxy(false)
|
||||
, m_recentErroredTorrentsTimer(new QTimer(this))
|
||||
{
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
|
||||
initResumeFolder();
|
||||
|
||||
m_recentErroredTorrentsTimer->setSingleShot(true);
|
||||
m_recentErroredTorrentsTimer->setInterval(1000);
|
||||
connect(m_recentErroredTorrentsTimer, &QTimer::timeout, this, [this]() { m_recentErroredTorrents.clear(); });
|
||||
|
||||
m_seedingLimitTimer = new QTimer(this);
|
||||
m_seedingLimitTimer->setInterval(10000);
|
||||
connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
|
||||
@@ -523,14 +511,13 @@ Session::Session(QObject *parent)
|
||||
|
||||
enableTracker(isTrackerEnabled());
|
||||
|
||||
connect(Net::ProxyConfigurationManager::instance(), &Net::ProxyConfigurationManager::proxyConfigurationChanged
|
||||
, this, &Session::configureDeferred);
|
||||
connect(Net::ProxyConfigurationManager::instance(), SIGNAL(proxyConfigurationChanged()), SLOT(configureDeferred()));
|
||||
|
||||
// Network configuration monitor
|
||||
connect(&m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &Session::networkOnlineStateChanged);
|
||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &Session::networkConfigurationChange);
|
||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &Session::networkConfigurationChange);
|
||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange);
|
||||
connect(&m_networkManager, SIGNAL(onlineStateChanged(bool)), SLOT(networkOnlineStateChanged(bool)));
|
||||
connect(&m_networkManager, SIGNAL(configurationAdded(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||
connect(&m_networkManager, SIGNAL(configurationRemoved(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||
connect(&m_networkManager, SIGNAL(configurationChanged(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||
|
||||
m_ioThread = new QThread(this);
|
||||
m_resumeDataSavingManager = new ResumeDataSavingManager(m_resumeFolderPath);
|
||||
@@ -790,16 +777,14 @@ bool Session::removeCategory(const QString &name)
|
||||
bool result = false;
|
||||
if (isSubcategoriesEnabled()) {
|
||||
// remove subcategories
|
||||
const QString test = name + "/";
|
||||
Dict::removeIf(m_categories, [this, &test, &result](const QString &category, const QString &)
|
||||
{
|
||||
QString test = name + "/";
|
||||
foreach (const QString &category, m_categories.keys()) {
|
||||
if (category.startsWith(test)) {
|
||||
m_categories.remove(category);
|
||||
result = true;
|
||||
emit categoryRemoved(category);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result = (m_categories.remove(name) > 0) || result;
|
||||
@@ -946,7 +931,7 @@ qreal Session::globalMaxRatio() const
|
||||
return m_globalMaxRatio;
|
||||
}
|
||||
|
||||
// Torrents with a ratio superior to the given value will
|
||||
// Torrents will a ratio superior to the given value will
|
||||
// be automatically deleted
|
||||
void Session::setGlobalMaxRatio(qreal ratio)
|
||||
{
|
||||
@@ -1010,7 +995,7 @@ void Session::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,7 +1074,7 @@ void Session::processBannedIPs(libt::ip_filter &filter)
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
void Session::adjustLimits(libt::settings_pack &settingsPack)
|
||||
{
|
||||
// Internally increase the queue limits to ensure that the magnet is started
|
||||
//Internally increase the queue limits to ensure that the magnet is started
|
||||
int maxDownloads = maxActiveDownloads();
|
||||
int maxActive = maxActiveTorrents();
|
||||
|
||||
@@ -1186,7 +1171,7 @@ void Session::initMetrics()
|
||||
|
||||
void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
{
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString chosenIP;
|
||||
@@ -1255,7 +1240,7 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_int(libt::settings_pack::allowed_enc_level, libt::settings_pack::pe_rc4);
|
||||
settingsPack.set_bool(libt::settings_pack::prefer_rc4, true);
|
||||
switch (encryption()) {
|
||||
case 0: // Enabled
|
||||
case 0: //Enabled
|
||||
settingsPack.set_int(libt::settings_pack::out_enc_policy, libt::settings_pack::pe_enabled);
|
||||
settingsPack.set_int(libt::settings_pack::in_enc_policy, libt::settings_pack::pe_enabled);
|
||||
break;
|
||||
@@ -1308,7 +1293,7 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_bool(libt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
|
||||
settingsPack.set_bool(libt::settings_pack::announce_to_all_tiers, announceToAllTiers());
|
||||
|
||||
const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
|
||||
const int cacheSize = (diskCacheSize() > -1) ? diskCacheSize() * 64 : -1;
|
||||
settingsPack.set_int(libt::settings_pack::cache_size, cacheSize);
|
||||
settingsPack.set_int(libt::settings_pack::cache_expiry, diskCacheTTL());
|
||||
qDebug() << "Using a disk cache size of" << cacheSize << "MiB";
|
||||
@@ -1318,10 +1303,6 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_int(libt::settings_pack::disk_io_read_mode, mode);
|
||||
settingsPack.set_int(libt::settings_pack::disk_io_write_mode, mode);
|
||||
settingsPack.set_bool(libt::settings_pack::guided_read_cache, isGuidedReadCacheEnabled());
|
||||
|
||||
settingsPack.set_bool(libt::settings_pack::coalesce_reads, isCoalesceReadWriteEnabled());
|
||||
settingsPack.set_bool(libt::settings_pack::coalesce_writes, isCoalesceReadWriteEnabled());
|
||||
|
||||
settingsPack.set_int(libt::settings_pack::suggest_mode, isSuggestModeEnabled()
|
||||
? libt::settings_pack::suggest_read_cache : libt::settings_pack::no_piece_suggestions);
|
||||
|
||||
@@ -1337,9 +1318,6 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
|
||||
settingsPack.set_int(libt::settings_pack::active_seeds, maxActiveUploads());
|
||||
settingsPack.set_bool(libt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
|
||||
settingsPack.set_int(libt::settings_pack::inactive_down_rate, downloadRateForSlowTorrents() * 1024); // KiB to Bytes
|
||||
settingsPack.set_int(libt::settings_pack::inactive_up_rate, uploadRateForSlowTorrents() * 1024); // KiB to Bytes
|
||||
settingsPack.set_int(libt::settings_pack::auto_manage_startup, slowTorrentsInactivityTimer());
|
||||
}
|
||||
else {
|
||||
settingsPack.set_int(libt::settings_pack::active_downloads, -1);
|
||||
@@ -1516,12 +1494,12 @@ void Session::configurePeerClasses()
|
||||
|
||||
void Session::adjustLimits(libt::session_settings &sessionSettings)
|
||||
{
|
||||
// Internally increase the queue limits to ensure that the magnet is started
|
||||
//Internally increase the queue limits to ensure that the magnet is started
|
||||
int maxDownloads = maxActiveDownloads();
|
||||
int maxActive = maxActiveTorrents();
|
||||
|
||||
sessionSettings.active_downloads = (maxDownloads > -1) ? (maxDownloads + m_extraLimit) : maxDownloads;
|
||||
sessionSettings.active_limit = (maxActive > -1) ? (maxActive + m_extraLimit) : maxActive;
|
||||
sessionSettings.active_downloads = maxDownloads > -1 ? maxDownloads + m_extraLimit : maxDownloads;
|
||||
sessionSettings.active_limit = maxActive > -1 ? maxActive + m_extraLimit : maxActive;
|
||||
}
|
||||
|
||||
void Session::applyBandwidthLimits(libt::session_settings &sessionSettings)
|
||||
@@ -1540,7 +1518,7 @@ void Session::configure(libtorrent::session_settings &sessionSettings)
|
||||
encryptionSettings.allowed_enc_level = libt::pe_settings::rc4;
|
||||
encryptionSettings.prefer_rc4 = true;
|
||||
switch (encryption()) {
|
||||
case 0: // Enabled
|
||||
case 0: //Enabled
|
||||
encryptionSettings.out_enc_policy = libt::pe_settings::enabled;
|
||||
encryptionSettings.in_enc_policy = libt::pe_settings::enabled;
|
||||
break;
|
||||
@@ -1595,7 +1573,7 @@ void Session::configure(libtorrent::session_settings &sessionSettings)
|
||||
|
||||
sessionSettings.announce_to_all_trackers = announceToAllTrackers();
|
||||
sessionSettings.announce_to_all_tiers = announceToAllTiers();
|
||||
const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
|
||||
const int cacheSize = (diskCacheSize() > -1) ? diskCacheSize() * 64 : -1;
|
||||
sessionSettings.cache_size = cacheSize;
|
||||
sessionSettings.cache_expiry = diskCacheTTL();
|
||||
qDebug() << "Using a disk cache size of" << cacheSize << "MiB";
|
||||
@@ -1768,7 +1746,7 @@ void Session::enableBandwidthScheduler()
|
||||
void Session::populateAdditionalTrackers()
|
||||
{
|
||||
m_additionalTrackerList.clear();
|
||||
foreach (QString tracker, additionalTrackers().split('\n')) {
|
||||
foreach (QString tracker, additionalTrackers().split("\n")) {
|
||||
tracker = tracker.trimmed();
|
||||
if (!tracker.isEmpty())
|
||||
m_additionalTrackerList << tracker;
|
||||
@@ -1792,7 +1770,7 @@ void Session::processShareLimits()
|
||||
qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
|
||||
|
||||
if ((ratio <= TorrentHandle::MAX_RATIO) && (ratio >= ratioLimit)) {
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
if (m_maxRatioAction == Remove) {
|
||||
logger->addMessage(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash());
|
||||
@@ -1801,7 +1779,6 @@ void Session::processShareLimits()
|
||||
torrent->pause();
|
||||
logger->addMessage(tr("'%1' reached the maximum ratio you set. Paused.").arg(torrent->name()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1817,7 +1794,7 @@ void Session::processShareLimits()
|
||||
qDebug("Seeding Time: %d (limit: %d)", seedingTimeInMinutes, seedingTimeLimit);
|
||||
|
||||
if ((seedingTimeInMinutes <= TorrentHandle::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit)) {
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
if (m_maxRatioAction == Remove) {
|
||||
logger->addMessage(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash());
|
||||
@@ -2010,8 +1987,8 @@ void Session::decreaseTorrentsPriority(const QStringList &hashes)
|
||||
torrentQueue.pop();
|
||||
}
|
||||
|
||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
||||
foreach (const InfoHash &hash, m_loadedMetadata.keys())
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(hash));
|
||||
}
|
||||
|
||||
void Session::topTorrentsPriority(const QStringList &hashes)
|
||||
@@ -2055,8 +2032,8 @@ void Session::bottomTorrentsPriority(const QStringList &hashes)
|
||||
torrentQueue.pop();
|
||||
}
|
||||
|
||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
||||
foreach (const InfoHash &hash, m_loadedMetadata.keys())
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(hash));
|
||||
}
|
||||
|
||||
QHash<InfoHash, TorrentHandle *> Session::torrents() const
|
||||
@@ -2080,10 +2057,9 @@ bool Session::addTorrent(QString source, const AddTorrentParams ¶ms)
|
||||
Logger::instance()->addMessage(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
|
||||
// Launch downloader
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true);
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &Session::handleDownloadFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &Session::handleDownloadFailed);
|
||||
connect(handler, &Net::DownloadHandler::redirectedToMagnet, this, &Session::handleRedirectedToMagnet);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleDownloadFinished(QString, QString)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailed(QString, QString)));
|
||||
connect(handler, SIGNAL(redirectedToMagnet(QString, QString)), this, SLOT(handleRedirectedToMagnet(QString, QString)));
|
||||
m_downloadedTorrents[handler->url()] = params;
|
||||
}
|
||||
else {
|
||||
@@ -2154,26 +2130,12 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
||||
p = magnetUri.addTorrentParams();
|
||||
}
|
||||
else if (torrentInfo.isValid()) {
|
||||
if (!addData.resumed) {
|
||||
if (!addData.hasRootFolder)
|
||||
torrentInfo.stripRootFolder();
|
||||
|
||||
// Metadata
|
||||
if (!addData.hasSeedStatus)
|
||||
findIncompleteFiles(torrentInfo, savePath);
|
||||
|
||||
// if torrent name wasn't explicitly set we handle the case of
|
||||
// initial renaming of torrent content and rename torrent accordingly
|
||||
if (addData.name.isEmpty()) {
|
||||
QString contentName = torrentInfo.rootFolder();
|
||||
if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
|
||||
contentName = torrentInfo.fileName(0);
|
||||
|
||||
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
|
||||
addData.name = contentName;
|
||||
}
|
||||
}
|
||||
if (!addData.resumed && !addData.hasRootFolder)
|
||||
torrentInfo.stripRootFolder();
|
||||
|
||||
// Metadata
|
||||
if (!addData.resumed && !addData.hasSeedStatus)
|
||||
findIncompleteFiles(torrentInfo, savePath);
|
||||
p.ti = torrentInfo.nativeInfo();
|
||||
hash = torrentInfo.hash();
|
||||
}
|
||||
@@ -2279,7 +2241,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri)
|
||||
InfoHash hash = magnetUri.hash();
|
||||
QString name = magnetUri.name();
|
||||
|
||||
// We should not add torrent if it's already
|
||||
// We should not add torrent if it already
|
||||
// processed or adding to session
|
||||
if (m_torrents.contains(hash)) return false;
|
||||
if (m_addingTorrents.contains(hash)) return false;
|
||||
@@ -2302,7 +2264,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri)
|
||||
p.max_connections = maxConnectionsPerTorrent();
|
||||
p.max_uploads = maxUploadsPerTorrent();
|
||||
|
||||
QString savePath = QString("%1/%2").arg(QDir::tempPath(), hash);
|
||||
QString savePath = QString("%1/%2").arg(QDir::tempPath()).arg(hash);
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
|
||||
// Forced start
|
||||
@@ -2375,7 +2337,9 @@ void Session::saveResumeData()
|
||||
std::vector<libt::alert *> alerts;
|
||||
getPendingAlerts(alerts, 30 * 1000);
|
||||
if (alerts.empty()) {
|
||||
fprintf(stderr, " aborting with %d outstanding torrents to save resume data for\n", m_numResumeData);
|
||||
std::cerr << " aborting with " << m_numResumeData
|
||||
<< " outstanding torrents to save resume data for"
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2424,7 +2388,7 @@ void Session::networkOnlineStateChanged(const bool online)
|
||||
Logger::instance()->addMessage(tr("System network status changed to %1", "e.g: System network status changed to ONLINE").arg(online ? tr("ONLINE") : tr("OFFLINE")), Log::INFO);
|
||||
}
|
||||
|
||||
void Session::networkConfigurationChange(const QNetworkConfiguration &cfg)
|
||||
void Session::networkConfigurationChange(const QNetworkConfiguration& cfg)
|
||||
{
|
||||
const QString configuredInterfaceName = networkInterface();
|
||||
// Empty means "Any Interface". In this case libtorrent has binded to 0.0.0.0 so any change to any interface will
|
||||
@@ -2450,7 +2414,7 @@ void Session::networkConfigurationChange(const QNetworkConfiguration &cfg)
|
||||
|
||||
const QStringList Session::getListeningIPs()
|
||||
{
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
QStringList IPs;
|
||||
|
||||
const QString ifaceName = networkInterface();
|
||||
@@ -2493,12 +2457,12 @@ const QStringList Session::getListeningIPs()
|
||||
ip = entry.ip();
|
||||
ipString = ip.toString();
|
||||
protocol = ip.protocol();
|
||||
Q_ASSERT((protocol == QAbstractSocket::IPv4Protocol) || (protocol == QAbstractSocket::IPv6Protocol));
|
||||
Q_ASSERT(protocol == QAbstractSocket::IPv4Protocol || protocol == QAbstractSocket::IPv6Protocol);
|
||||
if ((!listenIPv6 && (protocol == QAbstractSocket::IPv6Protocol))
|
||||
|| (listenIPv6 && (protocol == QAbstractSocket::IPv4Protocol)))
|
||||
continue;
|
||||
|
||||
// If an iface address has been defined to only allow ip's that match it to go through
|
||||
//If an iface address has been defined only allow ip's that match it to go through
|
||||
if (!ifaceAddr.isEmpty()) {
|
||||
if (ifaceAddr == ipString) {
|
||||
IPs.append(ipString);
|
||||
@@ -2529,7 +2493,7 @@ void Session::configureListeningInterface()
|
||||
const ushort port = this->port();
|
||||
qDebug() << Q_FUNC_INFO << port;
|
||||
|
||||
Logger *const logger = Logger::instance();
|
||||
Logger* const logger = Logger::instance();
|
||||
|
||||
std::pair<int, int> ports(port, port);
|
||||
libt::error_code ec;
|
||||
@@ -2965,7 +2929,7 @@ void Session::setMaxConnectionsPerTorrent(int max)
|
||||
try {
|
||||
handle.set_max_connections(max);
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
catch (std::exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2987,7 +2951,7 @@ void Session::setMaxUploadsPerTorrent(int max)
|
||||
try {
|
||||
handle.set_max_uploads(max);
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
catch (std::exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3085,19 +3049,6 @@ void Session::setGuidedReadCacheEnabled(bool enabled)
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
bool Session::isCoalesceReadWriteEnabled() const
|
||||
{
|
||||
return m_coalesceReadWriteEnabled;
|
||||
}
|
||||
|
||||
void Session::setCoalesceReadWriteEnabled(bool enabled)
|
||||
{
|
||||
if (enabled == m_coalesceReadWriteEnabled) return;
|
||||
|
||||
m_coalesceReadWriteEnabled = enabled;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
bool Session::isSuggestModeEnabled() const
|
||||
{
|
||||
return m_isSuggestMode;
|
||||
@@ -3234,48 +3185,6 @@ void Session::setIgnoreSlowTorrentsForQueueing(bool ignore)
|
||||
}
|
||||
}
|
||||
|
||||
int Session::downloadRateForSlowTorrents() const
|
||||
{
|
||||
return m_downloadRateForSlowTorrents;
|
||||
}
|
||||
|
||||
void Session::setDownloadRateForSlowTorrents(int rateInKibiBytes)
|
||||
{
|
||||
if (rateInKibiBytes == m_downloadRateForSlowTorrents)
|
||||
return;
|
||||
|
||||
m_downloadRateForSlowTorrents = rateInKibiBytes;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
int Session::uploadRateForSlowTorrents() const
|
||||
{
|
||||
return m_uploadRateForSlowTorrents;
|
||||
}
|
||||
|
||||
void Session::setUploadRateForSlowTorrents(int rateInKibiBytes)
|
||||
{
|
||||
if (rateInKibiBytes == m_uploadRateForSlowTorrents)
|
||||
return;
|
||||
|
||||
m_uploadRateForSlowTorrents = rateInKibiBytes;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
int Session::slowTorrentsInactivityTimer() const
|
||||
{
|
||||
return m_slowTorrentsInactivityTimer;
|
||||
}
|
||||
|
||||
void Session::setSlowTorrentsInactivityTimer(int timeInSeconds)
|
||||
{
|
||||
if (timeInSeconds == m_slowTorrentsInactivityTimer)
|
||||
return;
|
||||
|
||||
m_slowTorrentsInactivityTimer = timeInSeconds;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
int Session::outgoingPortsMin() const
|
||||
{
|
||||
return m_outgoingPortsMin;
|
||||
@@ -3532,7 +3441,7 @@ void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QStrin
|
||||
emit torrentTagRemoved(torrent, tag);
|
||||
}
|
||||
|
||||
void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent)
|
||||
void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
|
||||
{
|
||||
emit torrentSavingModeChanged(torrent);
|
||||
}
|
||||
@@ -3540,7 +3449,7 @@ void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent)
|
||||
void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QList<TrackerEntry> &newTrackers)
|
||||
{
|
||||
foreach (const TrackerEntry &newTracker, newTrackers)
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url(), torrent->name()));
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url()).arg(torrent->name()));
|
||||
emit trackersAdded(torrent, newTrackers);
|
||||
if (torrent->trackers().size() == newTrackers.size())
|
||||
emit trackerlessStateChanged(torrent, false);
|
||||
@@ -3550,7 +3459,7 @@ void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QLi
|
||||
void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const QList<TrackerEntry> &deletedTrackers)
|
||||
{
|
||||
foreach (const TrackerEntry &deletedTracker, deletedTrackers)
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name()));
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url()).arg(torrent->name()));
|
||||
emit trackersRemoved(torrent, deletedTrackers);
|
||||
if (torrent->trackers().size() == 0)
|
||||
emit trackerlessStateChanged(torrent, true);
|
||||
@@ -3565,13 +3474,13 @@ void Session::handleTorrentTrackersChanged(TorrentHandle *const torrent)
|
||||
void Session::handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QList<QUrl> &newUrlSeeds)
|
||||
{
|
||||
foreach (const QUrl &newUrlSeed, newUrlSeeds)
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString()).arg(torrent->name()));
|
||||
}
|
||||
|
||||
void Session::handleTorrentUrlSeedsRemoved(TorrentHandle *const torrent, const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
foreach (const QUrl &urlSeed, urlSeeds)
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString()).arg(torrent->name()));
|
||||
}
|
||||
|
||||
void Session::handleTorrentMetadataReceived(TorrentHandle *const torrent)
|
||||
@@ -3734,8 +3643,8 @@ void Session::enableIPFilter()
|
||||
// set between clearing the old one and setting the new one.
|
||||
if (!m_filterParser) {
|
||||
m_filterParser = new FilterParserThread(this);
|
||||
connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &Session::handleIPFilterParsed);
|
||||
connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &Session::handleIPFilterError);
|
||||
connect(m_filterParser.data(), SIGNAL(IPFilterParsed(int)), SLOT(handleIPFilterParsed(int)));
|
||||
connect(m_filterParser.data(), SIGNAL(IPFilterError()), SLOT(handleIPFilterError()));
|
||||
}
|
||||
m_filterParser->processFilterFile(IPFilterFile());
|
||||
}
|
||||
@@ -3768,7 +3677,7 @@ void Session::recursiveTorrentDownload(const InfoHash &hash)
|
||||
Logger::instance()->addMessage(
|
||||
tr("Recursive download of file '%1' embedded in torrent '%2'"
|
||||
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
|
||||
.arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
|
||||
.arg(Utils::Fs::toNativePath(torrentRelpath)).arg(torrent->name()));
|
||||
const QString torrentFullpath = torrent->savePath() + "/" + torrentRelpath;
|
||||
|
||||
AddTorrentParams params;
|
||||
@@ -3818,7 +3727,7 @@ void Session::startUpTorrents()
|
||||
.arg(params.hash), Log::CRITICAL);
|
||||
|
||||
// process add torrent messages before message queue overflow
|
||||
if ((resumedTorrentsCount % 100) == 0) readAlerts();
|
||||
if (resumedTorrentsCount % 100 == 0) readAlerts();
|
||||
|
||||
++resumedTorrentsCount;
|
||||
};
|
||||
@@ -3853,12 +3762,12 @@ void Session::startUpTorrents()
|
||||
}
|
||||
else {
|
||||
int q = queuePosition;
|
||||
for (; queuedResumeData.contains(q); ++q) {
|
||||
for(; queuedResumeData.contains(q); ++q) {
|
||||
}
|
||||
if (q != queuePosition) {
|
||||
++numOfRemappedFiles;
|
||||
}
|
||||
queuedResumeData[q] = {hash, magnetUri, resumeData, data};
|
||||
queuedResumeData[q] = { hash, magnetUri, resumeData, data };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4167,8 +4076,8 @@ void Session::handleTorrentDeleteFailedAlert(libt::torrent_delete_failed_alert *
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(tmpRemovingTorrentData.savePathToRemove);
|
||||
|
||||
LogMsg(tr("'%1' was removed from the transfer list but the files couldn't be deleted. Error: %2", "'xxx.avi' was removed...")
|
||||
.arg(tmpRemovingTorrentData.name, QString::fromLocal8Bit(p->error.message().c_str()))
|
||||
, Log::CRITICAL);
|
||||
.arg(tmpRemovingTorrentData.name)
|
||||
.arg(QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||
}
|
||||
|
||||
void Session::handleMetadataReceivedAlert(libt::metadata_received_alert *p)
|
||||
@@ -4189,15 +4098,10 @@ void Session::handleFileErrorAlert(libt::file_error_alert *p)
|
||||
// NOTE: Check this function!
|
||||
TorrentHandle *const torrent = m_torrents.value(p->handle.info_hash());
|
||||
if (torrent) {
|
||||
const InfoHash hash = torrent->hash();
|
||||
if (!m_recentErroredTorrents.contains(hash)) {
|
||||
m_recentErroredTorrents.insert(hash);
|
||||
const QString msg = QString::fromStdString(p->message());
|
||||
LogMsg(tr("An I/O error occurred, '%1' paused. %2").arg(torrent->name(), msg));
|
||||
emit fullDiskError(torrent, msg);
|
||||
}
|
||||
|
||||
m_recentErroredTorrentsTimer->start();
|
||||
QString msg = QString::fromStdString(p->message());
|
||||
Logger::instance()->addMessage(tr("An I/O error occurred, '%1' paused. %2")
|
||||
.arg(torrent->name()).arg(msg));
|
||||
emit fullDiskError(torrent, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4272,9 +4176,7 @@ void Session::handleListenSucceededAlert(libt::listen_succeeded_alert *p)
|
||||
else if (p->sock_type == libt::listen_succeeded_alert::tcp_ssl)
|
||||
proto = "TCP_SSL";
|
||||
qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||
Logger::instance()->addMessage(
|
||||
tr("qBittorrent is successfully listening on interface %1 port: %2/%3", "e.g: qBittorrent is successfully listening on interface 192.168.0.1 port: TCP/6881")
|
||||
.arg(p->endpoint.address().to_string(ec).c_str(), proto, QString::number(p->endpoint.port())), Log::INFO);
|
||||
Logger::instance()->addMessage(tr("qBittorrent is successfully listening on interface %1 port: %2/%3", "e.g: qBittorrent is successfully listening on interface 192.168.0.1 port: TCP/6881").arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port())), Log::INFO);
|
||||
|
||||
// Force reannounce on all torrents because some trackers blacklist some ports
|
||||
std::vector<libt::torrent_handle> torrents = m_nativeSession->get_torrents();
|
||||
@@ -4300,11 +4202,10 @@ void Session::handleListenFailedAlert(libt::listen_failed_alert *p)
|
||||
proto = "SOCKS5";
|
||||
qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||
Logger::instance()->addMessage(
|
||||
tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4.",
|
||||
"e.g: qBittorrent failed listening on interface 192.168.0.1 port: TCP/6881. Reason: already in use.")
|
||||
.arg(p->endpoint.address().to_string(ec).c_str(), proto, QString::number(p->endpoint.port())
|
||||
, QString::fromLocal8Bit(p->error.message().c_str()))
|
||||
, Log::CRITICAL);
|
||||
tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4.",
|
||||
"e.g: qBittorrent failed listening on interface 192.168.0.1 port: TCP/6881. Reason: already in use.")
|
||||
.arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port()))
|
||||
.arg(QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||
}
|
||||
|
||||
void Session::handleExternalIPAlert(libt::external_ip_alert *p)
|
||||
@@ -4365,10 +4266,11 @@ void Session::handleSessionStatsAlert(libt::session_stats_alert *p)
|
||||
m_status.diskWriteQueue = p->values[m_metricIndices.peer.numPeersDownDisk];
|
||||
m_status.peersCount = p->values[m_metricIndices.peer.numPeersConnected];
|
||||
|
||||
const int numBlocksRead = p->values[m_metricIndices.disk.numBlocksRead];
|
||||
const int numBlocksCacheHits = p->values[m_metricIndices.disk.numBlocksCacheHits];
|
||||
const auto numBlocksRead = p->values[m_metricIndices.disk.numBlocksRead];
|
||||
m_cacheStatus.totalUsedBuffers = p->values[m_metricIndices.disk.diskBlocksInUse];
|
||||
m_cacheStatus.readRatio = static_cast<qreal>(numBlocksCacheHits) / std::max(numBlocksCacheHits + numBlocksRead, 1);
|
||||
m_cacheStatus.readRatio = numBlocksRead > 0
|
||||
? static_cast<qreal>(p->values[m_metricIndices.disk.numBlocksCacheHits]) / numBlocksRead
|
||||
: -1;
|
||||
m_cacheStatus.jobQueueLength = p->values[m_metricIndices.disk.queuedDiskJobs];
|
||||
|
||||
quint64 totalJobs = p->values[m_metricIndices.disk.writeJobs] + p->values[m_metricIndices.disk.readJobs]
|
||||
@@ -4568,11 +4470,11 @@ namespace
|
||||
return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
|
||||
|
||||
using PCONVERTIFACENAMETOLUID = NETIO_STATUS (WINAPI *)(const WCHAR *, PNET_LUID);
|
||||
const auto ConvertIfaceNameToLuid = Utils::Misc::loadWinAPI<PCONVERTIFACENAMETOLUID>("Iphlpapi.dll", "ConvertInterfaceNameToLuidW");
|
||||
PCONVERTIFACENAMETOLUID ConvertIfaceNameToLuid = reinterpret_cast<PCONVERTIFACENAMETOLUID>(::GetProcAddress(::GetModuleHandleW(L"Iphlpapi.dll"), "ConvertInterfaceNameToLuidW"));
|
||||
if (!ConvertIfaceNameToLuid) return QString();
|
||||
|
||||
using PCONVERTIFACELUIDTOGUID = NETIO_STATUS (WINAPI *)(const NET_LUID *, GUID *);
|
||||
const auto ConvertIfaceLuidToGuid = Utils::Misc::loadWinAPI<PCONVERTIFACELUIDTOGUID>("Iphlpapi.dll", "ConvertInterfaceLuidToGuid");
|
||||
PCONVERTIFACELUIDTOGUID ConvertIfaceLuidToGuid = reinterpret_cast<PCONVERTIFACELUIDTOGUID>(::GetProcAddress(::GetModuleHandleW(L"Iphlpapi.dll"), "ConvertInterfaceLuidToGuid"));
|
||||
if (!ConvertIfaceLuidToGuid) return QString();
|
||||
|
||||
NET_LUID luid;
|
||||
|
||||
@@ -30,14 +30,18 @@
|
||||
#ifndef BITTORRENT_SESSION_H
|
||||
#define BITTORRENT_SESSION_H
|
||||
|
||||
#include <vector>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
#include <QMutex>
|
||||
#endif
|
||||
#include <QNetworkConfigurationManager>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
@@ -45,12 +49,6 @@
|
||||
#include <QVector>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
#include <QMutex>
|
||||
#else
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
|
||||
#include "base/settingvalue.h"
|
||||
#include "base/tristatebool.h"
|
||||
#include "base/types.h"
|
||||
@@ -113,6 +111,7 @@ class QTimer;
|
||||
class QStringList;
|
||||
class QString;
|
||||
class QUrl;
|
||||
template<typename T> class QList;
|
||||
|
||||
class FilterParserThread;
|
||||
class BandwidthScheduler;
|
||||
@@ -383,8 +382,6 @@ namespace BitTorrent
|
||||
void setUseOSCache(bool use);
|
||||
bool isGuidedReadCacheEnabled() const;
|
||||
void setGuidedReadCacheEnabled(bool enabled);
|
||||
bool isCoalesceReadWriteEnabled() const;
|
||||
void setCoalesceReadWriteEnabled(bool enabled);
|
||||
bool isSuggestModeEnabled() const;
|
||||
void setSuggestMode(bool mode);
|
||||
int sendBufferWatermark() const;
|
||||
@@ -399,12 +396,6 @@ namespace BitTorrent
|
||||
void setQueueingSystemEnabled(bool enabled);
|
||||
bool ignoreSlowTorrentsForQueueing() const;
|
||||
void setIgnoreSlowTorrentsForQueueing(bool ignore);
|
||||
int downloadRateForSlowTorrents() const;
|
||||
void setDownloadRateForSlowTorrents(int rateInKibiBytes);
|
||||
int uploadRateForSlowTorrents() const;
|
||||
void setUploadRateForSlowTorrents(int rateInKibiBytes);
|
||||
int slowTorrentsInactivityTimer() const;
|
||||
void setSlowTorrentsInactivityTimer(int timeInSeconds);
|
||||
int outgoingPortsMin() const;
|
||||
void setOutgoingPortsMin(int min);
|
||||
int outgoingPortsMax() const;
|
||||
@@ -563,7 +554,7 @@ namespace BitTorrent
|
||||
bool requestedFileDeletion;
|
||||
};
|
||||
|
||||
explicit Session(QObject *parent = nullptr);
|
||||
explicit Session(QObject *parent = 0);
|
||||
~Session();
|
||||
|
||||
bool hasPerTorrentRatioLimit() const;
|
||||
@@ -655,7 +646,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_diskCacheTTL;
|
||||
CachedSettingValue<bool> m_useOSCache;
|
||||
CachedSettingValue<bool> m_guidedReadCacheEnabled;
|
||||
CachedSettingValue<bool> m_coalesceReadWriteEnabled;
|
||||
CachedSettingValue<bool> m_isSuggestMode;
|
||||
CachedSettingValue<int> m_sendBufferWatermark;
|
||||
CachedSettingValue<int> m_sendBufferLowWatermark;
|
||||
@@ -666,9 +656,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_maxActiveUploads;
|
||||
CachedSettingValue<int> m_maxActiveTorrents;
|
||||
CachedSettingValue<bool> m_ignoreSlowTorrentsForQueueing;
|
||||
CachedSettingValue<int> m_downloadRateForSlowTorrents;
|
||||
CachedSettingValue<int> m_uploadRateForSlowTorrents;
|
||||
CachedSettingValue<int> m_slowTorrentsInactivityTimer;
|
||||
CachedSettingValue<int> m_outgoingPortsMin;
|
||||
CachedSettingValue<int> m_outgoingPortsMax;
|
||||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||
@@ -761,10 +748,6 @@ namespace BitTorrent
|
||||
QStringMap m_categories;
|
||||
QSet<QString> m_tags;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<InfoHash> m_recentErroredTorrents;
|
||||
QTimer *m_recentErroredTorrentsTimer;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
QMutex m_alertsMutex;
|
||||
QWaitCondition m_alertsWaitCondition;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,6 +24,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "torrentcreatorthread.h"
|
||||
@@ -35,14 +37,9 @@
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/storage.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
@@ -89,56 +86,13 @@ void TorrentCreatorThread::run()
|
||||
emit updateProgress(0);
|
||||
|
||||
try {
|
||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + "/";
|
||||
|
||||
// Adding files to the torrent
|
||||
libt::file_storage fs;
|
||||
if (QFileInfo(m_params.inputPath).isFile()) {
|
||||
libt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
|
||||
}
|
||||
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()) {
|
||||
dirIter.next();
|
||||
dirs += dirIter.filePath();
|
||||
}
|
||||
std::sort(dirs.begin(), dirs.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||
|
||||
QStringList fileNames;
|
||||
QHash<QString, boost::int64_t> fileSizeMap;
|
||||
|
||||
for (const auto &dir : qAsConst(dirs)) {
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter(dir, QDir::Files);
|
||||
while (fileIter.hasNext()) {
|
||||
fileIter.next();
|
||||
|
||||
const QString relFilePath = fileIter.filePath().mid(parentPath.length());
|
||||
tmpNames += relFilePath;
|
||||
fileSizeMap[relFilePath] = fileIter.fileInfo().size();
|
||||
}
|
||||
|
||||
std::sort(tmpNames.begin(), tmpNames.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||
fileNames += tmpNames;
|
||||
}
|
||||
|
||||
for (const auto &fileName : qAsConst(fileNames))
|
||||
fs.add_file(fileName.toStdString(), fileSizeMap[fileName]);
|
||||
}
|
||||
// Adding files to the torrent
|
||||
libt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
|
||||
|
||||
if (isInterruptionRequested()) return;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
libt::create_torrent newTorrent(fs, m_params.pieceSize, -1
|
||||
, (m_params.isAlignmentOptimized ? libt::create_torrent::optimize : 0));
|
||||
#else
|
||||
libt::create_torrent newTorrent(fs, m_params.pieceSize, -1
|
||||
, (m_params.isAlignmentOptimized ? libt::create_torrent::optimize_alignment : 0));
|
||||
#endif
|
||||
libt::create_torrent newTorrent(fs, m_params.pieceSize);
|
||||
|
||||
// Add url seeds
|
||||
foreach (QString seed, m_params.urlSeeds) {
|
||||
@@ -158,6 +112,7 @@ void TorrentCreatorThread::run()
|
||||
if (isInterruptionRequested()) return;
|
||||
|
||||
// calculate the hash for all pieces
|
||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + "/";
|
||||
libt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
|
||||
, [this, &newTorrent](const int n) { sendProgressSignal(n, newTorrent.num_pieces()); });
|
||||
// Set qBittorrent as creator and add user comment to
|
||||
@@ -201,19 +156,12 @@ void TorrentCreatorThread::run()
|
||||
}
|
||||
}
|
||||
|
||||
int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const bool isAlignmentOptimized)
|
||||
int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize)
|
||||
{
|
||||
if (inputPath.isEmpty())
|
||||
return 0;
|
||||
|
||||
libt::file_storage fs;
|
||||
libt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter);
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
return libt::create_torrent(fs, pieceSize, -1
|
||||
, (isAlignmentOptimized ? libt::create_torrent::optimize : 0)).num_pieces();
|
||||
#else
|
||||
return libt::create_torrent(fs, pieceSize, -1
|
||||
, (isAlignmentOptimized ? libt::create_torrent::optimize_alignment : 0)).num_pieces();
|
||||
#endif
|
||||
return libt::create_torrent(fs, pieceSize).num_pieces();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,6 +24,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef BITTORRENT_TORRENTCREATORTHREAD_H
|
||||
@@ -37,7 +39,6 @@ namespace BitTorrent
|
||||
struct TorrentCreatorParams
|
||||
{
|
||||
bool isPrivate;
|
||||
bool isAlignmentOptimized;
|
||||
int pieceSize;
|
||||
QString inputPath;
|
||||
QString savePath;
|
||||
@@ -57,7 +58,7 @@ namespace BitTorrent
|
||||
|
||||
void create(const TorrentCreatorParams ¶ms);
|
||||
|
||||
static int calculateTotalPieces(const QString &inputPath, const int pieceSize, const bool isAlignmentOptimized);
|
||||
static int calculateTotalPieces(const QString &inputPath, const int pieceSize);
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
#include "session.h"
|
||||
#include "trackerentry.h"
|
||||
|
||||
const QString QB_EXT {QStringLiteral(".!qB")};
|
||||
const QString QB_EXT {".!qB"};
|
||||
|
||||
namespace libt = libtorrent;
|
||||
using namespace BitTorrent;
|
||||
@@ -156,6 +156,8 @@ const int TorrentHandle::MAX_SEEDING_TIME = 525600;
|
||||
// The following can be removed after one or two libtorrent releases on each branch.
|
||||
namespace
|
||||
{
|
||||
const char i18nContext[] = "TorrentHandle";
|
||||
|
||||
// new constructor is available
|
||||
template<typename T, typename std::enable_if<std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
||||
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||
@@ -519,19 +521,15 @@ int TorrentHandle::piecesHave() const
|
||||
|
||||
qreal TorrentHandle::progress() const
|
||||
{
|
||||
if (!isChecking()) {
|
||||
if (!m_nativeStatus.total_wanted)
|
||||
return 0.;
|
||||
if (!m_nativeStatus.total_wanted)
|
||||
return 0.;
|
||||
|
||||
if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
|
||||
return 1.;
|
||||
if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
|
||||
return 1.;
|
||||
|
||||
qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
|
||||
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
|
||||
return progress;
|
||||
}
|
||||
|
||||
return m_nativeStatus.progress;
|
||||
float progress = static_cast<float>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
|
||||
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
|
||||
return progress;
|
||||
}
|
||||
|
||||
QString TorrentHandle::category() const
|
||||
@@ -738,8 +736,7 @@ bool TorrentHandle::isActive() const
|
||||
|| m_state == TorrentState::Downloading
|
||||
|| m_state == TorrentState::ForcedDownloading
|
||||
|| m_state == TorrentState::Uploading
|
||||
|| m_state == TorrentState::ForcedUploading
|
||||
|| m_state == TorrentState::Moving;
|
||||
|| m_state == TorrentState::ForcedUploading;
|
||||
}
|
||||
|
||||
bool TorrentHandle::isInactive() const
|
||||
@@ -781,18 +778,28 @@ bool TorrentHandle::hasFirstLastPiecePriority() const
|
||||
if (!hasMetadata())
|
||||
return m_needsToSetFirstLastPiecePriority;
|
||||
|
||||
const std::vector<int> filePriorities = nativeHandle().file_priorities();
|
||||
for (int i = 0; i < static_cast<int>(filePriorities.size()); ++i) {
|
||||
if (filePriorities[i] <= 0)
|
||||
continue;
|
||||
// Get int first media file
|
||||
std::vector<int> fp;
|
||||
fp = m_nativeHandle.file_priorities();
|
||||
|
||||
const TorrentInfo::PieceRange extremities = info().filePieces(i);
|
||||
const int firstPiecePrio = nativeHandle().piece_priority(extremities.first());
|
||||
const int lastPiecePrio = nativeHandle().piece_priority(extremities.last());
|
||||
return ((firstPiecePrio == 7) && (lastPiecePrio == 7));
|
||||
TorrentInfo::PieceRange extremities;
|
||||
bool found = false;
|
||||
int count = static_cast<int>(fp.size());
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const QString ext = Utils::Fs::fileExtension(filePath(i));
|
||||
if (Utils::Misc::isPreviewable(ext) && (fp[i] > 0)) {
|
||||
extremities = info().filePieces(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!found) return false; // No media file
|
||||
|
||||
int first = m_nativeHandle.piece_priority(extremities.first());
|
||||
int last = m_nativeHandle.piece_priority(extremities.last());
|
||||
|
||||
return ((first == 7) && (last == 7));
|
||||
}
|
||||
|
||||
TorrentState TorrentHandle::state() const
|
||||
@@ -802,10 +809,7 @@ TorrentState TorrentHandle::state() const
|
||||
|
||||
void TorrentHandle::updateState()
|
||||
{
|
||||
if (isMoveInProgress()) {
|
||||
m_state = TorrentState::Moving;
|
||||
}
|
||||
else if (isPaused()) {
|
||||
if (isPaused()) {
|
||||
if (hasMissingFiles())
|
||||
m_state = TorrentState::MissingFiles;
|
||||
else if (hasError())
|
||||
@@ -1273,7 +1277,7 @@ void TorrentHandle::forceRecheck()
|
||||
|
||||
if (isPaused()) {
|
||||
m_pauseAfterRecheck = true;
|
||||
resume_impl(true, true);
|
||||
resume();
|
||||
}
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
@@ -1292,37 +1296,39 @@ void TorrentHandle::toggleSequentialDownload()
|
||||
setSequentialDownload(!isSequentialDownload());
|
||||
}
|
||||
|
||||
void TorrentHandle::setFirstLastPiecePriority(const bool enabled)
|
||||
void TorrentHandle::setFirstLastPiecePriority(bool b)
|
||||
{
|
||||
if (!hasMetadata()) {
|
||||
m_needsToSetFirstLastPiecePriority = enabled;
|
||||
m_needsToSetFirstLastPiecePriority = b;
|
||||
return;
|
||||
}
|
||||
|
||||
// Download first and last pieces first for every file in the torrent
|
||||
const std::vector<int> filePriorities = nativeHandle().file_priorities();
|
||||
std::vector<int> piecePriorities = nativeHandle().piece_priorities();
|
||||
for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index) {
|
||||
const int filePrio = filePriorities[index];
|
||||
if (filePrio <= 0)
|
||||
continue;
|
||||
std::vector<int> fp = m_nativeHandle.file_priorities();
|
||||
std::vector<int> pp = m_nativeHandle.piece_priorities();
|
||||
|
||||
// Determine the priority to set
|
||||
const int newPrio = enabled ? 7 : filePrio;
|
||||
const TorrentInfo::PieceRange extremities = info().filePieces(index);
|
||||
// Download first and last pieces first for all media files in the torrent
|
||||
int nbfiles = static_cast<int>(fp.size());
|
||||
for (int index = 0; index < nbfiles; ++index) {
|
||||
const QString path = filePath(index);
|
||||
const QString ext = Utils::Fs::fileExtension(path);
|
||||
if (Utils::Misc::isPreviewable(ext) && (fp[index] > 0)) {
|
||||
qDebug() << "File" << path << "is previewable, toggle downloading of first/last pieces first";
|
||||
|
||||
// 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) {
|
||||
piecePriorities[extremities.first() + i] = newPrio;
|
||||
piecePriorities[extremities.last() - i] = newPrio;
|
||||
// Determine the priority to set
|
||||
int prio = b ? 7 : fp[index];
|
||||
|
||||
TorrentInfo::PieceRange extremities = info().filePieces(index);
|
||||
|
||||
// worst case: AVI index = 1% of total file size (at the end of the file)
|
||||
int nNumPieces = ceil(fileSize(index) * 0.01 / pieceLength());
|
||||
for (int i = 0; i < nNumPieces; ++i) {
|
||||
pp[extremities.first() + i] = prio;
|
||||
pp[extremities.last() - i] = prio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_nativeHandle.prioritize_pieces(piecePriorities);
|
||||
|
||||
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
||||
.arg((enabled ? tr("On") : tr("Off")), name()));
|
||||
m_nativeHandle.prioritize_pieces(pp);
|
||||
}
|
||||
|
||||
void TorrentHandle::toggleFirstLastPiecePriority()
|
||||
@@ -1339,17 +1345,12 @@ void TorrentHandle::pause()
|
||||
}
|
||||
|
||||
void TorrentHandle::resume(bool forced)
|
||||
{
|
||||
resume_impl(forced, false);
|
||||
}
|
||||
|
||||
void TorrentHandle::resume_impl(bool forced, bool uploadMode)
|
||||
{
|
||||
if (hasError())
|
||||
m_nativeHandle.clear_error();
|
||||
m_hasMissingFiles = false;
|
||||
m_nativeHandle.set_upload_mode(false);
|
||||
m_nativeHandle.auto_managed(!forced);
|
||||
m_nativeHandle.set_upload_mode(uploadMode);
|
||||
m_nativeHandle.resume();
|
||||
}
|
||||
|
||||
@@ -1370,7 +1371,6 @@ void TorrentHandle::moveStorage(const QString &newPath, bool overwrite)
|
||||
, (overwrite ? libt::always_replace_files : libt::dont_replace));
|
||||
m_moveStorageInfo.oldPath = oldPath;
|
||||
m_moveStorageInfo.newPath = newPath;
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1420,7 +1420,7 @@ void TorrentHandle::handleStateUpdate(const libt::torrent_status &nativeStatus)
|
||||
updateStatus(nativeStatus);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_alert *p)
|
||||
void TorrentHandle::handleStorageMovedAlert(libtorrent::storage_moved_alert *p)
|
||||
{
|
||||
if (!isMoveInProgress()) {
|
||||
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
||||
@@ -1437,8 +1437,8 @@ void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_aler
|
||||
return;
|
||||
}
|
||||
|
||||
LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), m_moveStorageInfo.newPath));
|
||||
|
||||
qDebug("Torrent is successfully moved from %s to %s"
|
||||
, qUtf8Printable(m_moveStorageInfo.oldPath), qUtf8Printable(m_moveStorageInfo.newPath));
|
||||
const QDir oldDir {m_moveStorageInfo.oldPath};
|
||||
if ((oldDir == QDir(m_session->torrentTempPath(info())))
|
||||
&& (oldDir != QDir(m_session->tempPath()))) {
|
||||
@@ -1447,10 +1447,9 @@ void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_aler
|
||||
qDebug() << "Removing torrent temp folder:" << m_moveStorageInfo.oldPath;
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(m_moveStorageInfo.oldPath);
|
||||
}
|
||||
|
||||
m_moveStorageInfo.newPath.clear();
|
||||
updateStatus();
|
||||
|
||||
m_moveStorageInfo.newPath.clear();
|
||||
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
||||
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
||||
m_moveStorageInfo.queuedPath.clear();
|
||||
@@ -1465,19 +1464,17 @@ void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_aler
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
|
||||
void TorrentHandle::handleStorageMovedFailedAlert(const libtorrent::storage_moved_failed_alert *p)
|
||||
void TorrentHandle::handleStorageMovedFailedAlert(libtorrent::storage_moved_failed_alert *p)
|
||||
{
|
||||
if (!isMoveInProgress()) {
|
||||
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
||||
return;
|
||||
}
|
||||
|
||||
LogMsg(tr("Could not move torrent: '%1'. Reason: %2")
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
|
||||
LogMsg(QCoreApplication::translate(i18nContext, "Could not move torrent: '%1'. Reason: %2")
|
||||
.arg(name()).arg(QString::fromStdString(p->message())), Log::CRITICAL);
|
||||
|
||||
m_moveStorageInfo.newPath.clear();
|
||||
updateStatus();
|
||||
|
||||
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
||||
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
||||
m_moveStorageInfo.queuedPath.clear();
|
||||
@@ -1487,7 +1484,7 @@ void TorrentHandle::handleStorageMovedFailedAlert(const libtorrent::storage_move
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTrackerReplyAlert(const libtorrent::tracker_reply_alert *p)
|
||||
void TorrentHandle::handleTrackerReplyAlert(libtorrent::tracker_reply_alert *p)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
QString trackerUrl = QString::fromStdString(p->url);
|
||||
@@ -1502,32 +1499,32 @@ void TorrentHandle::handleTrackerReplyAlert(const libtorrent::tracker_reply_aler
|
||||
m_session->handleTorrentTrackerReply(this, trackerUrl);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTrackerWarningAlert(const libtorrent::tracker_warning_alert *p)
|
||||
void TorrentHandle::handleTrackerWarningAlert(libtorrent::tracker_warning_alert *p)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
const QString trackerUrl = QString::fromStdString(p->url);
|
||||
const QString message = QString::fromStdString(p->msg);
|
||||
QString trackerUrl = QString::fromStdString(p->url);
|
||||
QString message = QString::fromStdString(p->msg);
|
||||
#else
|
||||
const QString trackerUrl = p->tracker_url();
|
||||
const QString message = p->warning_message();
|
||||
QString trackerUrl(p->tracker_url());
|
||||
QString message = QString::fromStdString(p->message());
|
||||
#endif
|
||||
|
||||
qDebug("Received a tracker warning for %s: %s", qUtf8Printable(trackerUrl), qUtf8Printable(message));
|
||||
// Connection was successful now but there is a warning message
|
||||
m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message
|
||||
|
||||
m_session->handleTorrentTrackerWarning(this, trackerUrl);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTrackerErrorAlert(const libtorrent::tracker_error_alert *p)
|
||||
void TorrentHandle::handleTrackerErrorAlert(libtorrent::tracker_error_alert *p)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
const QString trackerUrl = QString::fromStdString(p->url);
|
||||
const QString message = QString::fromStdString(p->msg);
|
||||
QString trackerUrl = QString::fromStdString(p->url);
|
||||
QString message = QString::fromStdString(p->msg);
|
||||
#else
|
||||
const QString trackerUrl = p->tracker_url();
|
||||
const QString message = p->error_message();
|
||||
QString trackerUrl(p->tracker_url());
|
||||
QString message = QString::fromStdString(p->message());
|
||||
#endif
|
||||
|
||||
qDebug("Received a tracker error for %s: %s", qUtf8Printable(trackerUrl), qUtf8Printable(message));
|
||||
m_trackerInfos[trackerUrl].lastMessage = message;
|
||||
|
||||
if (p->status_code == 401)
|
||||
@@ -1536,7 +1533,7 @@ void TorrentHandle::handleTrackerErrorAlert(const libtorrent::tracker_error_aler
|
||||
m_session->handleTorrentTrackerError(this, trackerUrl);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_alert *p)
|
||||
void TorrentHandle::handleTorrentCheckedAlert(libtorrent::torrent_checked_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
qDebug("%s have just finished checking", qUtf8Printable(hash()));
|
||||
@@ -1559,7 +1556,7 @@ void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_
|
||||
m_session->handleTorrentChecked(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p)
|
||||
void TorrentHandle::handleTorrentFinishedAlert(libtorrent::torrent_finished_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
qDebug("Got a torrent finished alert for %s", qUtf8Printable(name()));
|
||||
@@ -1586,7 +1583,7 @@ void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finishe
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p)
|
||||
void TorrentHandle::handleTorrentPausedAlert(libtorrent::torrent_paused_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
updateStatus();
|
||||
@@ -1594,13 +1591,13 @@ void TorrentHandle::handleTorrentPausedAlert(const libtorrent::torrent_paused_al
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p)
|
||||
void TorrentHandle::handleTorrentResumedAlert(libtorrent::torrent_resumed_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
m_session->handleTorrentResumed(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p)
|
||||
void TorrentHandle::handleSaveResumeDataAlert(libtorrent::save_resume_data_alert *p)
|
||||
{
|
||||
const bool useDummyResumeData = !(p && p->resume_data);
|
||||
libtorrent::entry dummyEntry;
|
||||
@@ -1634,35 +1631,36 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resume_data_failed_alert *p)
|
||||
void TorrentHandle::handleSaveResumeDataFailedAlert(libtorrent::save_resume_data_failed_alert *p)
|
||||
{
|
||||
// if torrent has no metadata we should save dummy fastresume data
|
||||
// containing Magnet URI and qBittorrent own resume data only
|
||||
if (p->error.value() == libt::errors::no_metadata)
|
||||
handleSaveResumeDataAlert(nullptr);
|
||||
handleSaveResumeDataAlert(0);
|
||||
else
|
||||
m_session->handleTorrentResumeDataFailed(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
|
||||
void TorrentHandle::handleFastResumeRejectedAlert(libtorrent::fastresume_rejected_alert *p)
|
||||
{
|
||||
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
|
||||
Logger *const logger = Logger::instance();
|
||||
|
||||
updateStatus();
|
||||
if (p->error.value() == libt::errors::mismatching_file_size) {
|
||||
// Mismatching file size (files were probably moved)
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
logger->addMessage(QCoreApplication::translate(i18nContext, "File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
m_hasMissingFiles = true;
|
||||
if (!isPaused())
|
||||
pause();
|
||||
}
|
||||
else {
|
||||
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
|
||||
logger->addMessage(QCoreApplication::translate(i18nContext, "Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
||||
.arg(name()).arg(QString::fromStdString(p->message())), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p)
|
||||
void TorrentHandle::handleFileRenamedAlert(libtorrent::file_renamed_alert *p)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name));
|
||||
@@ -1681,7 +1679,7 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
|
||||
QString newPath = newPathParts.join("/");
|
||||
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
|
||||
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath));
|
||||
oldPath = QString("%1/%2").arg(savePath(true), oldPath);
|
||||
oldPath = QString("%1/%2").arg(savePath(true)).arg(oldPath);
|
||||
qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath));
|
||||
QDir().rmpath(oldPath);
|
||||
}
|
||||
@@ -1694,7 +1692,7 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p)
|
||||
void TorrentHandle::handleFileRenameFailedAlert(libtorrent::file_rename_failed_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
|
||||
@@ -1703,7 +1701,7 @@ void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_fa
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p)
|
||||
void TorrentHandle::handleFileCompletedAlert(libtorrent::file_completed_alert *p)
|
||||
{
|
||||
updateStatus();
|
||||
|
||||
@@ -1719,7 +1717,7 @@ void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_al
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleStatsAlert(const libtorrent::stats_alert *p)
|
||||
void TorrentHandle::handleStatsAlert(libtorrent::stats_alert *p)
|
||||
{
|
||||
Q_ASSERT(p->interval >= 1000);
|
||||
SpeedSample transferred(p->transferred[libt::stats_alert::download_payload] * 1000LL / p->interval,
|
||||
@@ -1727,7 +1725,7 @@ void TorrentHandle::handleStatsAlert(const libtorrent::stats_alert *p)
|
||||
m_speedMonitor.addSample(transferred);
|
||||
}
|
||||
|
||||
void TorrentHandle::handleMetadataReceivedAlert(const libt::metadata_received_alert *p)
|
||||
void TorrentHandle::handleMetadataReceivedAlert(libt::metadata_received_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
||||
|
||||
@@ -149,8 +149,6 @@ namespace BitTorrent
|
||||
PausedDownloading,
|
||||
PausedUploading,
|
||||
|
||||
Moving,
|
||||
|
||||
MissingFiles,
|
||||
Error
|
||||
};
|
||||
@@ -333,7 +331,7 @@ namespace BitTorrent
|
||||
void setName(const QString &name);
|
||||
void setSequentialDownload(bool b);
|
||||
void toggleSequentialDownload();
|
||||
void setFirstLastPiecePriority(bool enabled);
|
||||
void setFirstLastPiecePriority(bool b);
|
||||
void toggleFirstLastPiecePriority();
|
||||
void pause();
|
||||
void resume(bool forced = false);
|
||||
@@ -390,25 +388,24 @@ namespace BitTorrent
|
||||
void updateState();
|
||||
void updateTorrentInfo();
|
||||
|
||||
void handleStorageMovedAlert(const libtorrent::storage_moved_alert *p);
|
||||
void handleStorageMovedFailedAlert(const libtorrent::storage_moved_failed_alert *p);
|
||||
void handleTrackerReplyAlert(const libtorrent::tracker_reply_alert *p);
|
||||
void handleTrackerWarningAlert(const libtorrent::tracker_warning_alert *p);
|
||||
void handleTrackerErrorAlert(const libtorrent::tracker_error_alert *p);
|
||||
void handleTorrentCheckedAlert(const libtorrent::torrent_checked_alert *p);
|
||||
void handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p);
|
||||
void handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p);
|
||||
void handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p);
|
||||
void handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p);
|
||||
void handleSaveResumeDataFailedAlert(const libtorrent::save_resume_data_failed_alert *p);
|
||||
void handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p);
|
||||
void handleFileRenamedAlert(const libtorrent::file_renamed_alert *p);
|
||||
void handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p);
|
||||
void handleFileCompletedAlert(const libtorrent::file_completed_alert *p);
|
||||
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
|
||||
void handleStatsAlert(const libtorrent::stats_alert *p);
|
||||
void handleStorageMovedAlert(libtorrent::storage_moved_alert *p);
|
||||
void handleStorageMovedFailedAlert(libtorrent::storage_moved_failed_alert *p);
|
||||
void handleTrackerReplyAlert(libtorrent::tracker_reply_alert *p);
|
||||
void handleTrackerWarningAlert(libtorrent::tracker_warning_alert *p);
|
||||
void handleTrackerErrorAlert(libtorrent::tracker_error_alert *p);
|
||||
void handleTorrentCheckedAlert(libtorrent::torrent_checked_alert *p);
|
||||
void handleTorrentFinishedAlert(libtorrent::torrent_finished_alert *p);
|
||||
void handleTorrentPausedAlert(libtorrent::torrent_paused_alert *p);
|
||||
void handleTorrentResumedAlert(libtorrent::torrent_resumed_alert *p);
|
||||
void handleSaveResumeDataAlert(libtorrent::save_resume_data_alert *p);
|
||||
void handleSaveResumeDataFailedAlert(libtorrent::save_resume_data_failed_alert *p);
|
||||
void handleFastResumeRejectedAlert(libtorrent::fastresume_rejected_alert *p);
|
||||
void handleFileRenamedAlert(libtorrent::file_renamed_alert *p);
|
||||
void handleFileRenameFailedAlert(libtorrent::file_rename_failed_alert *p);
|
||||
void handleFileCompletedAlert(libtorrent::file_completed_alert *p);
|
||||
void handleMetadataReceivedAlert(libtorrent::metadata_received_alert *p);
|
||||
void handleStatsAlert(libtorrent::stats_alert *p);
|
||||
|
||||
void resume_impl(bool forced, bool uploadMode);
|
||||
bool isMoveInProgress() const;
|
||||
QString nativeActualSavePath() const;
|
||||
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "torrentinfo.h"
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <libtorrent/error_code.hpp>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "infohash.h"
|
||||
#include "trackerentry.h"
|
||||
#include "torrentinfo.h"
|
||||
|
||||
namespace libt = libtorrent;
|
||||
using namespace BitTorrent;
|
||||
@@ -60,77 +60,23 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
|
||||
return *this;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString &error)
|
||||
{
|
||||
error.clear();
|
||||
libt::error_code ec;
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec)));
|
||||
if (error) {
|
||||
if (ec)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
else
|
||||
error->clear();
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(Utils::Fs::toNativePath(path).toStdString(), ec)));
|
||||
if (ec) {
|
||||
error = QString::fromUtf8(ec.message().c_str());
|
||||
qDebug("Cannot load .torrent file: %s", qUtf8Printable(error));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path)
|
||||
{
|
||||
if (error)
|
||||
error->clear();
|
||||
|
||||
QFile file {path};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (error)
|
||||
*error = file.errorString();
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
|
||||
if (file.size() > fileSizeLimit) {
|
||||
if (error)
|
||||
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const QByteArray data = file.read(fileSizeLimit);
|
||||
if (data.size() != file.size()) {
|
||||
if (error)
|
||||
*error = tr("Torrent file read error");
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
|
||||
// used in `torrent_info()` constructor
|
||||
const int depthLimit = 100;
|
||||
const int tokenLimit = 10000000;
|
||||
libt::error_code ec;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
libt::lazy_entry node;
|
||||
libt::lazy_bdecode(data.constData(), (data.constData() + data.size()), node, ec
|
||||
, nullptr, depthLimit, tokenLimit);
|
||||
#else
|
||||
libt::bdecode_node node;
|
||||
bdecode(data.constData(), (data.constData() + data.size()), node, ec
|
||||
, nullptr, depthLimit, tokenLimit);
|
||||
#endif
|
||||
if (ec) {
|
||||
if (error)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
TorrentInfo info {NativePtr(new libt::torrent_info(node, ec))};
|
||||
if (ec) {
|
||||
if (error)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
return info;
|
||||
QString error;
|
||||
return loadFromFile(path, error);
|
||||
}
|
||||
|
||||
bool TorrentInfo::isValid() const
|
||||
@@ -315,7 +261,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
|
||||
return hashes;
|
||||
}
|
||||
|
||||
TorrentInfo::PieceRange TorrentInfo::filePieces(const QString &file) const
|
||||
TorrentInfo::PieceRange TorrentInfo::filePieces(const QString& file) const
|
||||
{
|
||||
if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
|
||||
return {};
|
||||
@@ -353,8 +299,8 @@ void TorrentInfo::renameFile(uint index, const QString &newPath)
|
||||
|
||||
int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
||||
{
|
||||
// the check whether the object is valid is not needed here
|
||||
// because if filesCount() returns -1 the loop exits immediately
|
||||
// the check whether the object valid is not needed here
|
||||
// because filesCount() returns -1 in that case and the loop exits immediately
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
if (fileName == filePath(i))
|
||||
return i;
|
||||
@@ -362,29 +308,24 @@ int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString TorrentInfo::rootFolder() const
|
||||
bool TorrentInfo::hasRootFolder() const
|
||||
{
|
||||
QString rootFolder;
|
||||
QString testRootFolder;
|
||||
for (int i = 0; i < filesCount(); ++i) {
|
||||
const QString filePath = this->filePath(i);
|
||||
if (QDir::isAbsolutePath(filePath)) continue;
|
||||
|
||||
const auto filePathElements = filePath.splitRef('/');
|
||||
// if at least one file has no root folder, no common root folder exists
|
||||
if (filePathElements.count() <= 1) return "";
|
||||
if (filePathElements.count() <= 1) return false;
|
||||
|
||||
if (rootFolder.isEmpty())
|
||||
rootFolder = filePathElements.at(0).toString();
|
||||
else if (rootFolder != filePathElements.at(0))
|
||||
return "";
|
||||
if (testRootFolder.isEmpty())
|
||||
testRootFolder = filePathElements.at(0).toString();
|
||||
else if (testRootFolder != filePathElements.at(0))
|
||||
return false;
|
||||
}
|
||||
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
bool TorrentInfo::hasRootFolder() const
|
||||
{
|
||||
return !rootFolder().isEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TorrentInfo::stripRootFolder()
|
||||
|
||||
@@ -29,21 +29,20 @@
|
||||
#ifndef BITTORRENT_TORRENTINFO_H
|
||||
#define BITTORRENT_TORRENTINFO_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QtGlobal>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/indexrange.h"
|
||||
|
||||
class QByteArray;
|
||||
class QDateTime;
|
||||
class QString;
|
||||
class QStringList;
|
||||
class QUrl;
|
||||
class QDateTime;
|
||||
class QStringList;
|
||||
class QByteArray;
|
||||
template<typename T> class QList;
|
||||
template<typename T> class QVector;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -52,8 +51,6 @@ namespace BitTorrent
|
||||
|
||||
class TorrentInfo
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
|
||||
|
||||
public:
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
typedef boost::intrusive_ptr<const libtorrent::torrent_info> NativeConstPtr;
|
||||
@@ -66,8 +63,8 @@ namespace BitTorrent
|
||||
explicit TorrentInfo(NativeConstPtr nativeInfo = NativeConstPtr());
|
||||
TorrentInfo(const TorrentInfo &other);
|
||||
|
||||
static TorrentInfo load(const QByteArray &data, QString *error = nullptr) noexcept;
|
||||
static TorrentInfo loadFromFile(const QString &path, QString *error = nullptr) noexcept;
|
||||
static TorrentInfo loadFromFile(const QString &path, QString &error);
|
||||
static TorrentInfo loadFromFile(const QString &path);
|
||||
|
||||
TorrentInfo &operator=(const TorrentInfo &other);
|
||||
|
||||
@@ -104,7 +101,6 @@ namespace BitTorrent
|
||||
|
||||
void renameFile(uint index, const QString &newPath);
|
||||
|
||||
QString rootFolder() const;
|
||||
bool hasRootFolder() const;
|
||||
void stripRootFolder();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
@@ -27,18 +27,15 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "tracker.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "tracker.h"
|
||||
|
||||
// static limits
|
||||
static const int MAX_TORRENTS = 100;
|
||||
@@ -77,7 +74,7 @@ libtorrent::entry Peer::toEntry(bool noPeerId) const
|
||||
// Tracker
|
||||
|
||||
Tracker::Tracker(QObject *parent)
|
||||
: QObject(parent)
|
||||
: Http::ResponseBuilder(parent)
|
||||
, m_server(new Http::Server(this, this))
|
||||
{
|
||||
}
|
||||
@@ -133,30 +130,19 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
|
||||
|
||||
void Tracker::respondToAnnounceRequest()
|
||||
{
|
||||
QMap<QString, QByteArray> queryParams;
|
||||
// Parse GET parameters
|
||||
using namespace Utils::ByteArray;
|
||||
for (const QByteArray ¶m : copyAsConst(splitToViews(m_request.query, "&"))) {
|
||||
const int sepPos = param.indexOf('=');
|
||||
if (sepPos <= 0) continue; // ignores params without name
|
||||
|
||||
const QString paramName {QString::fromUtf8(param.constData(), sepPos)};
|
||||
const QByteArray paramValue {param.mid(sepPos + 1)};
|
||||
queryParams[paramName] = paramValue;
|
||||
}
|
||||
|
||||
const QStringMap &gets = m_request.gets;
|
||||
TrackerAnnounceRequest annonceReq;
|
||||
|
||||
// IP
|
||||
annonceReq.peer.ip = m_env.clientAddress.toString();
|
||||
|
||||
// 1. Get info_hash
|
||||
if (!queryParams.contains("info_hash")) {
|
||||
if (!gets.contains("info_hash")) {
|
||||
qDebug("Tracker: Missing info_hash");
|
||||
status(101, "Missing info_hash");
|
||||
return;
|
||||
}
|
||||
annonceReq.infoHash = queryParams.value("info_hash");
|
||||
annonceReq.infoHash = gets.value("info_hash");
|
||||
// info_hash cannot be longer than 20 bytes
|
||||
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
|
||||
qDebug("Tracker: Info_hash is not 20 byte long: %s (%d)", qUtf8Printable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
|
||||
@@ -165,12 +151,12 @@ void Tracker::respondToAnnounceRequest()
|
||||
}*/
|
||||
|
||||
// 2. Get peer ID
|
||||
if (!queryParams.contains("peer_id")) {
|
||||
if (!gets.contains("peer_id")) {
|
||||
qDebug("Tracker: Missing peer_id");
|
||||
status(102, "Missing peer_id");
|
||||
return;
|
||||
}
|
||||
annonceReq.peer.peerId = queryParams.value("peer_id");
|
||||
annonceReq.peer.peerId = gets.value("peer_id");
|
||||
// peer_id cannot be longer than 20 bytes
|
||||
/*if (annonce_req.peer.peer_id.length() > 20) {
|
||||
qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id));
|
||||
@@ -179,14 +165,14 @@ void Tracker::respondToAnnounceRequest()
|
||||
}*/
|
||||
|
||||
// 3. Get port
|
||||
if (!queryParams.contains("port")) {
|
||||
if (!gets.contains("port")) {
|
||||
qDebug("Tracker: Missing port");
|
||||
status(103, "Missing port");
|
||||
return;
|
||||
}
|
||||
bool ok = false;
|
||||
annonceReq.peer.port = queryParams.value("port").toInt(&ok);
|
||||
if (!ok || (annonceReq.peer.port < 0) || (annonceReq.peer.port > 65535)) {
|
||||
annonceReq.peer.port = gets.value("port").toInt(&ok);
|
||||
if (!ok || annonceReq.peer.port < 1 || annonceReq.peer.port > 65535) {
|
||||
qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port);
|
||||
status(103, "Missing port");
|
||||
return;
|
||||
@@ -194,15 +180,15 @@ void Tracker::respondToAnnounceRequest()
|
||||
|
||||
// 4. Get event
|
||||
annonceReq.event = "";
|
||||
if (queryParams.contains("event")) {
|
||||
annonceReq.event = queryParams.value("event");
|
||||
if (gets.contains("event")) {
|
||||
annonceReq.event = gets.value("event");
|
||||
qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event));
|
||||
}
|
||||
|
||||
// 5. Get numwant
|
||||
annonceReq.numwant = 50;
|
||||
if (queryParams.contains("numwant")) {
|
||||
int tmp = queryParams.value("numwant").toInt();
|
||||
if (gets.contains("numwant")) {
|
||||
int tmp = gets.value("numwant").toInt();
|
||||
if (tmp > 0) {
|
||||
qDebug("Tracker: numwant = %d", tmp);
|
||||
annonceReq.numwant = tmp;
|
||||
@@ -211,51 +197,37 @@ void Tracker::respondToAnnounceRequest()
|
||||
|
||||
// 6. no_peer_id (extension)
|
||||
annonceReq.noPeerId = false;
|
||||
if (queryParams.contains("no_peer_id"))
|
||||
if (gets.contains("no_peer_id"))
|
||||
annonceReq.noPeerId = true;
|
||||
|
||||
// 7. TODO: support "compact" extension
|
||||
|
||||
// Done parsing, now let's reply
|
||||
if (annonceReq.event == "stopped") {
|
||||
unregisterPeer(annonceReq);
|
||||
if (m_torrents.contains(annonceReq.infoHash)) {
|
||||
if (annonceReq.event == "stopped") {
|
||||
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
|
||||
m_torrents[annonceReq.infoHash].remove(annonceReq.peer.uid());
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
registerPeer(annonceReq);
|
||||
replyWithPeerList(annonceReq);
|
||||
}
|
||||
}
|
||||
|
||||
void Tracker::registerPeer(const TrackerAnnounceRequest &annonceReq)
|
||||
{
|
||||
if (annonceReq.peer.port == 0) return;
|
||||
|
||||
if (!m_torrents.contains(annonceReq.infoHash)) {
|
||||
// Unknown torrent
|
||||
if (m_torrents.size() == MAX_TORRENTS) {
|
||||
// Reached max size, remove a random torrent
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
}
|
||||
}
|
||||
|
||||
// Register the user
|
||||
PeerList &peers = m_torrents[annonceReq.infoHash];
|
||||
if (!peers.contains(annonceReq.peer.uid())) {
|
||||
// Unknown peer
|
||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||
// Too many peers, remove a random one
|
||||
peers.erase(peers.begin());
|
||||
}
|
||||
PeerList peers = m_torrents.value(annonceReq.infoHash);
|
||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||
// Too many peers, remove a random one
|
||||
peers.erase(peers.begin());
|
||||
}
|
||||
peers[annonceReq.peer.uid()] = annonceReq.peer;
|
||||
}
|
||||
m_torrents[annonceReq.infoHash] = peers;
|
||||
|
||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &annonceReq)
|
||||
{
|
||||
if (annonceReq.peer.port == 0) return;
|
||||
|
||||
if (m_torrents[annonceReq.infoHash].remove(annonceReq.peer.uid()) > 0)
|
||||
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
|
||||
// Reply
|
||||
replyWithPeerList(annonceReq);
|
||||
}
|
||||
|
||||
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
||||
@@ -263,18 +235,22 @@ void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
||||
// Prepare the entry for bencoding
|
||||
libtorrent::entry::dictionary_type replyDict;
|
||||
replyDict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
||||
|
||||
QList<Peer> peers = m_torrents.value(annonceReq.infoHash).values();
|
||||
libtorrent::entry::list_type peerList;
|
||||
for (const Peer &p : m_torrents.value(annonceReq.infoHash))
|
||||
foreach (const Peer &p, peers) {
|
||||
//if (p != annonce_req.peer)
|
||||
peerList.push_back(p.toEntry(annonceReq.noPeerId));
|
||||
}
|
||||
replyDict["peers"] = libtorrent::entry(peerList);
|
||||
|
||||
const libtorrent::entry replyEntry(replyDict);
|
||||
libtorrent::entry replyEntry(replyDict);
|
||||
// bencode
|
||||
QByteArray reply;
|
||||
libtorrent::bencode(std::back_inserter(reply), replyEntry);
|
||||
std::vector<char> buf;
|
||||
libtorrent::bencode(std::back_inserter(buf), replyEntry);
|
||||
QByteArray reply(&buf[0], static_cast<int>(buf.size()));
|
||||
qDebug("Tracker: reply with the following bencoded data:\n %s", reply.constData());
|
||||
|
||||
// HTTP reply
|
||||
print(reply, Http::CONTENT_TYPE_TXT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#define BITTORRENT_TRACKER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
#include "base/http/irequesthandler.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
@@ -52,7 +51,7 @@ namespace BitTorrent
|
||||
struct Peer
|
||||
{
|
||||
QString ip;
|
||||
QByteArray peerId;
|
||||
QString peerId;
|
||||
int port;
|
||||
|
||||
bool operator!=(const Peer &other) const;
|
||||
@@ -63,7 +62,7 @@ namespace BitTorrent
|
||||
|
||||
struct TrackerAnnounceRequest
|
||||
{
|
||||
QByteArray infoHash;
|
||||
QString infoHash;
|
||||
QString event;
|
||||
int numwant;
|
||||
Peer peer;
|
||||
@@ -72,11 +71,11 @@ namespace BitTorrent
|
||||
};
|
||||
|
||||
typedef QHash<QString, Peer> PeerList;
|
||||
typedef QHash<QByteArray, PeerList> TorrentList;
|
||||
typedef QHash<QString, PeerList> TorrentList;
|
||||
|
||||
/* Basic Bittorrent tracker implementation in Qt */
|
||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
||||
class Tracker : public QObject, public Http::IRequestHandler, private Http::ResponseBuilder
|
||||
class Tracker : public Http::ResponseBuilder, public Http::IRequestHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Tracker)
|
||||
@@ -90,8 +89,6 @@ namespace BitTorrent
|
||||
|
||||
private:
|
||||
void respondToAnnounceRequest();
|
||||
void registerPeer(const TrackerAnnounceRequest &annonceReq);
|
||||
void unregisterPeer(const TrackerAnnounceRequest &annonceReq);
|
||||
void replyWithPeerList(const TrackerAnnounceRequest &annonceReq);
|
||||
|
||||
Http::Server *m_server;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "exceptions.h"
|
||||
|
||||
RuntimeError::RuntimeError(const QString &message)
|
||||
: std::runtime_error {message.toUtf8().data()}
|
||||
, m_message {message}
|
||||
{
|
||||
}
|
||||
|
||||
QString RuntimeError::message() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
@@ -1,161 +1,176 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "filesystemwatcher.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#include <QSet>
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#include <cstring>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#include <string.h>
|
||||
#elif !defined Q_OS_HAIKU
|
||||
#include <sys/vfs.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "filesystemwatcher.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const int WATCH_INTERVAL = 10000; // 10 sec
|
||||
const int MAX_PARTIAL_RETRIES = 5;
|
||||
}
|
||||
#ifndef CIFS_MAGIC_NUMBER
|
||||
#define CIFS_MAGIC_NUMBER 0xFF534D42
|
||||
#endif
|
||||
|
||||
#ifndef NFS_SUPER_MAGIC
|
||||
#define NFS_SUPER_MAGIC 0x6969
|
||||
#endif
|
||||
|
||||
#ifndef SMB_SUPER_MAGIC
|
||||
#define SMB_SUPER_MAGIC 0x517B
|
||||
#endif
|
||||
|
||||
const int WATCH_INTERVAL = 10000; // 10 sec
|
||||
const int MAX_PARTIAL_RETRIES = 5;
|
||||
|
||||
FileSystemWatcher::FileSystemWatcher(QObject *parent)
|
||||
: QFileSystemWatcher(parent)
|
||||
{
|
||||
connect(this, &QFileSystemWatcher::directoryChanged, this, &FileSystemWatcher::scanLocalFolder);
|
||||
|
||||
m_partialTorrentTimer.setSingleShot(true);
|
||||
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
|
||||
m_filters << "*.torrent" << "*.magnet";
|
||||
connect(this, SIGNAL(directoryChanged(QString)), SLOT(scanLocalFolder(QString)));
|
||||
}
|
||||
|
||||
FileSystemWatcher::~FileSystemWatcher()
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
|
||||
if (m_watchTimer)
|
||||
delete m_watchTimer;
|
||||
#endif
|
||||
if (m_partialTorrentTimer)
|
||||
delete m_partialTorrentTimer;
|
||||
}
|
||||
|
||||
QStringList FileSystemWatcher::directories() const
|
||||
{
|
||||
QStringList dirs = QFileSystemWatcher::directories();
|
||||
QStringList dirs;
|
||||
#ifndef Q_OS_WIN
|
||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
||||
dirs << dir.canonicalPath();
|
||||
if (m_watchTimer) {
|
||||
foreach (const QDir &dir, m_watchedFolders)
|
||||
dirs << dir.canonicalPath();
|
||||
}
|
||||
#endif
|
||||
dirs << QFileSystemWatcher::directories();
|
||||
return dirs;
|
||||
}
|
||||
|
||||
void FileSystemWatcher::addPath(const QString &path)
|
||||
{
|
||||
if (path.isEmpty()) return;
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
const QDir dir(path);
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) return;
|
||||
|
||||
// Check if the path points to a network file system or not
|
||||
if (Utils::Fs::isNetworkFileSystem(path)) {
|
||||
if (isNetworkFileSystem(path)) {
|
||||
// Network mode
|
||||
qDebug("Network folder detected: %s", qUtf8Printable(path));
|
||||
qDebug("Using file polling mode instead of inotify...");
|
||||
m_watchedFolders << dir;
|
||||
|
||||
m_watchTimer.start(WATCH_INTERVAL);
|
||||
return;
|
||||
// Set up the watch timer
|
||||
if (!m_watchTimer) {
|
||||
m_watchTimer = new QTimer(this);
|
||||
connect(m_watchTimer, SIGNAL(timeout()), SLOT(scanNetworkFolders()));
|
||||
m_watchTimer->start(WATCH_INTERVAL); // 5 sec
|
||||
}
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
// Normal mode
|
||||
qDebug("FS Watching is watching %s in normal mode", qUtf8Printable(path));
|
||||
QFileSystemWatcher::addPath(path);
|
||||
scanLocalFolder(path);
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
}
|
||||
#endif
|
||||
|
||||
// Normal mode
|
||||
qDebug("FS Watcher is watching %s in normal mode", qUtf8Printable(path));
|
||||
QFileSystemWatcher::addPath(path);
|
||||
scanLocalFolder(path);
|
||||
}
|
||||
|
||||
void FileSystemWatcher::removePath(const QString &path)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
if (m_watchedFolders.removeOne(path)) {
|
||||
if (m_watchedFolders.isEmpty())
|
||||
m_watchTimer.stop();
|
||||
return;
|
||||
QDir dir(path);
|
||||
for (int i = 0; i < m_watchedFolders.count(); ++i) {
|
||||
if (QDir(m_watchedFolders.at(i)) == dir) {
|
||||
m_watchedFolders.removeAt(i);
|
||||
if (m_watchedFolders.isEmpty())
|
||||
delete m_watchTimer;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Normal mode
|
||||
QFileSystemWatcher::removePath(path);
|
||||
}
|
||||
|
||||
void FileSystemWatcher::scanLocalFolder(const QString &path)
|
||||
void FileSystemWatcher::scanLocalFolder(QString path)
|
||||
{
|
||||
QTimer::singleShot(2000, this, [this, path]() { processTorrentsInDir(path); });
|
||||
qDebug("scanLocalFolder(%s) called", qUtf8Printable(path));
|
||||
QStringList torrents;
|
||||
// Local folders scan
|
||||
addTorrentsFromDir(QDir(path), torrents);
|
||||
// Report detected torrent files
|
||||
if (!torrents.empty()) {
|
||||
qDebug("The following files are being reported: %s", qUtf8Printable(torrents.join("\n")));
|
||||
emit torrentsAdded(torrents);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
void FileSystemWatcher::scanNetworkFolders()
|
||||
{
|
||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
||||
processTorrentsInDir(dir);
|
||||
}
|
||||
#ifndef Q_OS_WIN
|
||||
qDebug("scanNetworkFolders() called");
|
||||
QStringList torrents;
|
||||
// Network folders scan
|
||||
foreach (const QDir &dir, m_watchedFolders) {
|
||||
//qDebug("FSWatcher: Polling manually folder %s", qUtf8Printable(dir.path()));
|
||||
addTorrentsFromDir(dir, torrents);
|
||||
}
|
||||
// Report detected torrent files
|
||||
if (!torrents.empty()) {
|
||||
qDebug("The following files are being reported: %s", qUtf8Printable(torrents.join("\n")));
|
||||
emit torrentsAdded(torrents);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileSystemWatcher::processPartialTorrents()
|
||||
{
|
||||
QStringList noLongerPartial;
|
||||
|
||||
// Check which torrents are still partial
|
||||
Dict::removeIf(m_partialTorrents, [&noLongerPartial](const QString &torrentPath, int &value)
|
||||
{
|
||||
if (!QFile::exists(torrentPath))
|
||||
return true;
|
||||
|
||||
if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) {
|
||||
foreach (const QString &torrentPath, m_partialTorrents.keys()) {
|
||||
if (!QFile::exists(torrentPath)) {
|
||||
m_partialTorrents.remove(torrentPath);
|
||||
}
|
||||
else if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) {
|
||||
noLongerPartial << torrentPath;
|
||||
return true;
|
||||
m_partialTorrents.remove(torrentPath);
|
||||
}
|
||||
|
||||
if (value >= MAX_PARTIAL_RETRIES) {
|
||||
QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
|
||||
return true;
|
||||
else if (m_partialTorrents[torrentPath] >= MAX_PARTIAL_RETRIES) {
|
||||
m_partialTorrents.remove(torrentPath);
|
||||
QFile::rename(torrentPath, torrentPath + ".invalid");
|
||||
}
|
||||
|
||||
++value;
|
||||
return false;
|
||||
});
|
||||
else {
|
||||
++m_partialTorrents[torrentPath];
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the partial timer if necessary
|
||||
if (m_partialTorrents.empty()) {
|
||||
m_partialTorrentTimer.stop();
|
||||
m_partialTorrentTimer->stop();
|
||||
m_partialTorrentTimer->deleteLater();
|
||||
qDebug("No longer any partial torrent.");
|
||||
}
|
||||
else {
|
||||
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
|
||||
m_partialTorrentTimer.start(WATCH_INTERVAL);
|
||||
m_partialTorrentTimer->start(WATCH_INTERVAL);
|
||||
}
|
||||
|
||||
// Notify of new torrents
|
||||
@@ -163,23 +178,99 @@ void FileSystemWatcher::processPartialTorrents()
|
||||
emit torrentsAdded(noLongerPartial);
|
||||
}
|
||||
|
||||
void FileSystemWatcher::processTorrentsInDir(const QDir &dir)
|
||||
void FileSystemWatcher::startPartialTorrentTimer()
|
||||
{
|
||||
QStringList torrents;
|
||||
const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files);
|
||||
for (const QString &file : files) {
|
||||
Q_ASSERT(!m_partialTorrents.isEmpty());
|
||||
if (!m_partialTorrentTimer) {
|
||||
m_partialTorrentTimer = new QTimer();
|
||||
connect(m_partialTorrentTimer, SIGNAL(timeout()), SLOT(processPartialTorrents()));
|
||||
m_partialTorrentTimer->setSingleShot(true);
|
||||
m_partialTorrentTimer->start(WATCH_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystemWatcher::addTorrentsFromDir(const QDir &dir, QStringList &torrents)
|
||||
{
|
||||
const QStringList files = dir.entryList(m_filters, QDir::Files, QDir::Unsorted);
|
||||
foreach (const QString &file, files) {
|
||||
const QString fileAbsPath = dir.absoluteFilePath(file);
|
||||
if (file.endsWith(".magnet"))
|
||||
if (fileAbsPath.endsWith(".magnet")) {
|
||||
torrents << fileAbsPath;
|
||||
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid())
|
||||
}
|
||||
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) {
|
||||
torrents << fileAbsPath;
|
||||
else if (!m_partialTorrents.contains(fileAbsPath))
|
||||
m_partialTorrents[fileAbsPath] = 0;
|
||||
}
|
||||
else if (!m_partialTorrents.contains(fileAbsPath)) {
|
||||
qDebug("Partial torrent detected at: %s", qUtf8Printable(fileAbsPath));
|
||||
qDebug("Delay the file's processing...");
|
||||
m_partialTorrents.insert(fileAbsPath, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!torrents.empty())
|
||||
emit torrentsAdded(torrents);
|
||||
|
||||
if (!m_partialTorrents.empty() && !m_partialTorrentTimer.isActive())
|
||||
m_partialTorrentTimer.start(WATCH_INTERVAL);
|
||||
if (!m_partialTorrents.empty())
|
||||
startPartialTorrentTimer();
|
||||
}
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
bool FileSystemWatcher::isNetworkFileSystem(QString path)
|
||||
{
|
||||
QString file = path;
|
||||
if (!file.endsWith("/"))
|
||||
file += "/";
|
||||
file += ".";
|
||||
struct statfs buf;
|
||||
if (!statfs(file.toLocal8Bit().constData(), &buf)) {
|
||||
#ifdef Q_OS_MAC
|
||||
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
|
||||
return ((strcmp(buf.f_fstypename, "nfs") == 0) || (strcmp(buf.f_fstypename, "cifs") == 0) || (strcmp(buf.f_fstypename, "smbfs") == 0));
|
||||
#else
|
||||
return ((buf.f_type == static_cast<long>(CIFS_MAGIC_NUMBER))
|
||||
|| (buf.f_type == static_cast<long>(NFS_SUPER_MAGIC))
|
||||
|| (buf.f_type == static_cast<long>(SMB_SUPER_MAGIC)));
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
std::cerr << "Error: statfs() call failed for " << qPrintable(file) << ". Supposing it is a local folder..." << std::endl;
|
||||
switch(errno) {
|
||||
case EACCES:
|
||||
std::cerr << "Search permission is denied for a component of the path prefix of the path" << std::endl;
|
||||
break;
|
||||
case EFAULT:
|
||||
std::cerr << "Buf or path points to an invalid address" << std::endl;
|
||||
break;
|
||||
case EINTR:
|
||||
std::cerr << "This call was interrupted by a signal" << std::endl;
|
||||
break;
|
||||
case EIO:
|
||||
std::cerr << "I/O Error" << std::endl;
|
||||
break;
|
||||
case ELOOP:
|
||||
std::cerr << "Too many symlinks" << std::endl;
|
||||
break;
|
||||
case ENAMETOOLONG:
|
||||
std::cerr << "path is too long" << std::endl;
|
||||
break;
|
||||
case ENOENT:
|
||||
std::cerr << "The file referred by path does not exist" << std::endl;
|
||||
break;
|
||||
case ENOMEM:
|
||||
std::cerr << "Insufficient kernel memory" << std::endl;
|
||||
break;
|
||||
case ENOSYS:
|
||||
std::cerr << "The file system does not detect this call" << std::endl;
|
||||
break;
|
||||
case ENOTDIR:
|
||||
std::cerr << "A component of the path is not a directory" << std::endl;
|
||||
break;
|
||||
case EOVERFLOW:
|
||||
std::cerr << "Some values were too large to be represented in the struct" << std::endl;
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unknown error" << std::endl;
|
||||
}
|
||||
|
||||
std::cerr << "Errno: " << errno << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef FILESYSTEMWATCHER_H
|
||||
#define FILESYSTEMWATCHER_H
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -45,6 +18,7 @@ class FileSystemWatcher : public QFileSystemWatcher
|
||||
|
||||
public:
|
||||
explicit FileSystemWatcher(QObject *parent = nullptr);
|
||||
~FileSystemWatcher();
|
||||
|
||||
QStringList directories() const;
|
||||
void addPath(const QString &path);
|
||||
@@ -54,23 +28,25 @@ signals:
|
||||
void torrentsAdded(const QStringList &pathList);
|
||||
|
||||
protected slots:
|
||||
void scanLocalFolder(const QString &path);
|
||||
void processPartialTorrents();
|
||||
#ifndef Q_OS_WIN
|
||||
void scanLocalFolder(QString path);
|
||||
void scanNetworkFolders();
|
||||
#endif
|
||||
void processPartialTorrents();
|
||||
|
||||
private:
|
||||
void processTorrentsInDir(const QDir &dir);
|
||||
|
||||
// Partial torrents
|
||||
QHash<QString, int> m_partialTorrents;
|
||||
QTimer m_partialTorrentTimer;
|
||||
void startPartialTorrentTimer();
|
||||
void addTorrentsFromDir(const QDir &dir, QStringList &torrents);
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
static bool isNetworkFileSystem(QString path);
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
QList<QDir> m_watchedFolders;
|
||||
QTimer m_watchTimer;
|
||||
QPointer<QTimer> m_watchTimer;
|
||||
#endif
|
||||
QStringList m_filters;
|
||||
// Partial torrents
|
||||
QHash<QString, int> m_partialTorrents;
|
||||
QPointer<QTimer> m_partialTorrentTimer;
|
||||
};
|
||||
|
||||
#endif // FILESYSTEMWATCHER_H
|
||||
|
||||
@@ -26,23 +26,5 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <QtGlobal>
|
||||
|
||||
const char C_TORRENT_FILE_EXTENSION[] = ".torrent";
|
||||
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
|
||||
template <typename T>
|
||||
constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
|
||||
|
||||
// prevent rvalue arguments:
|
||||
template <typename T>
|
||||
void qAsConst(const T &&) = delete;
|
||||
#endif
|
||||
|
||||
// returns a const object copy
|
||||
template <typename T>
|
||||
constexpr typename std::add_const<T>::type copyAsConst(T &&t) noexcept { return std::move(t); }
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -26,6 +25,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "connection.h"
|
||||
@@ -33,7 +34,6 @@
|
||||
#include <QRegExp>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "irequesthandler.h"
|
||||
#include "requestparser.h"
|
||||
#include "responsegenerator.h"
|
||||
@@ -47,7 +47,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||
{
|
||||
m_socket->setParent(this);
|
||||
m_idleTimer.start();
|
||||
connect(m_socket, &QTcpSocket::readyRead, this, &Connection::read);
|
||||
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
@@ -58,64 +58,37 @@ Connection::~Connection()
|
||||
void Connection::read()
|
||||
{
|
||||
m_idleTimer.restart();
|
||||
|
||||
m_receivedData.append(m_socket->readAll());
|
||||
Request request;
|
||||
RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request); // TODO: transform request headers to lowercase
|
||||
|
||||
while (!m_receivedData.isEmpty()) {
|
||||
const RequestParser::ParseResult result = RequestParser::parse(m_receivedData);
|
||||
switch (err) {
|
||||
case RequestParser::IncompleteRequest:
|
||||
// Partial request waiting for the rest
|
||||
break;
|
||||
|
||||
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) {
|
||||
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %ld, IP: %s")
|
||||
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
case RequestParser::BadRequest:
|
||||
sendResponse(Response(400, "Bad Request"));
|
||||
m_receivedData.clear();
|
||||
break;
|
||||
|
||||
Response resp(413, "Payload Too Large");
|
||||
resp.headers[HEADER_CONNECTION] = "close";
|
||||
case RequestParser::NoError:
|
||||
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
|
||||
|
||||
sendResponse(resp);
|
||||
m_socket->close();
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case RequestParser::ParseStatus::BadRequest: {
|
||||
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %s")
|
||||
.arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
|
||||
Response resp(400, "Bad Request");
|
||||
resp.headers[HEADER_CONNECTION] = "close";
|
||||
|
||||
sendResponse(resp);
|
||||
m_socket->close();
|
||||
}
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
if (acceptsGzipEncoding(result.request.headers["accept-encoding"]))
|
||||
resp.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
||||
|
||||
resp.headers[HEADER_CONNECTION] = "keep-alive";
|
||||
|
||||
sendResponse(resp);
|
||||
m_receivedData = m_receivedData.mid(result.frameSize);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
Response response = m_requestHandler->processRequest(request, env);
|
||||
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
||||
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
||||
sendResponse(response);
|
||||
m_receivedData.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::sendResponse(const Response &response) const
|
||||
void Connection::sendResponse(const Response &response)
|
||||
{
|
||||
m_socket->write(toByteArray(response));
|
||||
m_socket->close(); // TODO: remove when HTTP pipelining is supported
|
||||
}
|
||||
|
||||
bool Connection::hasExpired(const qint64 timeout) const
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -25,6 +25,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
|
||||
@@ -48,7 +50,7 @@ namespace Http
|
||||
Q_DISABLE_COPY(Connection)
|
||||
|
||||
public:
|
||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
||||
~Connection();
|
||||
|
||||
bool hasExpired(qint64 timeout) const;
|
||||
@@ -59,7 +61,7 @@ namespace Http
|
||||
|
||||
private:
|
||||
static bool acceptsGzipEncoding(QString codings);
|
||||
void sendResponse(const Response &response) const;
|
||||
void sendResponse(const Response &response);
|
||||
|
||||
QTcpSocket *m_socket;
|
||||
IRequestHandler *m_requestHandler;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "httperror.h"
|
||||
|
||||
HTTPError::HTTPError(int statusCode, const QString &statusText, const QString &message)
|
||||
: RuntimeError {message}
|
||||
, m_statusCode {statusCode}
|
||||
, m_statusText {statusText}
|
||||
{
|
||||
}
|
||||
|
||||
int HTTPError::statusCode() const
|
||||
{
|
||||
return m_statusCode;
|
||||
}
|
||||
|
||||
QString HTTPError::statusText() const
|
||||
{
|
||||
return m_statusText;
|
||||
}
|
||||
|
||||
BadRequestHTTPError::BadRequestHTTPError(const QString &message)
|
||||
: HTTPError(400, QLatin1String("Bad Request"), message)
|
||||
{
|
||||
}
|
||||
|
||||
ConflictHTTPError::ConflictHTTPError(const QString &message)
|
||||
: HTTPError(409, QLatin1String("Conflict"), message)
|
||||
{
|
||||
}
|
||||
|
||||
ForbiddenHTTPError::ForbiddenHTTPError(const QString &message)
|
||||
: HTTPError(403, QLatin1String("Forbidden"), message)
|
||||
{
|
||||
}
|
||||
|
||||
NotFoundHTTPError::NotFoundHTTPError(const QString &message)
|
||||
: HTTPError(404, QLatin1String("Not Found"), message)
|
||||
{
|
||||
}
|
||||
|
||||
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message)
|
||||
: HTTPError(415, QLatin1String("Unsupported Media Type"), message)
|
||||
{
|
||||
}
|
||||
|
||||
UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message)
|
||||
: HTTPError(401, QLatin1String("Unauthorized"), message)
|
||||
{
|
||||
}
|
||||
|
||||
InternalServerErrorHTTPError::InternalServerErrorHTTPError(const QString &message)
|
||||
: HTTPError(500, QLatin1String("Internal Server Error"), message)
|
||||
{
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/exceptions.h"
|
||||
|
||||
class HTTPError : public RuntimeError
|
||||
{
|
||||
public:
|
||||
HTTPError(int statusCode, const QString &statusText, const QString &message = "");
|
||||
|
||||
int statusCode() const;
|
||||
QString statusText() const;
|
||||
|
||||
private:
|
||||
const int m_statusCode;
|
||||
const QString m_statusText;
|
||||
};
|
||||
|
||||
class BadRequestHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit BadRequestHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class ForbiddenHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit ForbiddenHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class NotFoundHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit NotFoundHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class ConflictHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit ConflictHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class UnsupportedMediaTypeHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit UnsupportedMediaTypeHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class UnauthorizedHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit UnauthorizedHTTPError(const QString &message = "");
|
||||
};
|
||||
|
||||
class InternalServerErrorHTTPError : public HTTPError
|
||||
{
|
||||
public:
|
||||
explicit InternalServerErrorHTTPError(const QString &message = "");
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
@@ -31,215 +30,242 @@
|
||||
#include "requestparser.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <QDir>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/string.h"
|
||||
const QByteArray EOL("\r\n");
|
||||
const QByteArray EOH("\r\n\r\n");
|
||||
|
||||
inline QString unquoted(const QString &str)
|
||||
{
|
||||
if ((str[0] == '\"') && (str[str.length() - 1] == '\"'))
|
||||
return str.mid(1, str.length() - 2);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
using namespace Http;
|
||||
using namespace Utils::ByteArray;
|
||||
using QStringPair = QPair<QString, QString>;
|
||||
|
||||
namespace
|
||||
RequestParser::ErrorCode RequestParser::parse(const QByteArray &data, Request &request, uint maxContentLength)
|
||||
{
|
||||
const QByteArray EOH = QByteArray(CRLF).repeated(2);
|
||||
|
||||
const QByteArray viewWithoutEndingWith(const QByteArray &in, const QByteArray &str)
|
||||
{
|
||||
if (in.endsWith(str))
|
||||
return QByteArray::fromRawData(in.constData(), (in.size() - str.size()));
|
||||
return in;
|
||||
}
|
||||
|
||||
bool parseHeaderLine(const QString &line, QStringMap &out)
|
||||
{
|
||||
// [rfc7230] 3.2. Header Fields
|
||||
const int i = line.indexOf(':');
|
||||
if (i <= 0) {
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString name = line.leftRef(i).trimmed().toString().toLower();
|
||||
const QString value = line.midRef(i + 1).trimmed().toString();
|
||||
out[name] = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
return RequestParser(maxContentLength).parseHttpRequest(data, request);
|
||||
}
|
||||
|
||||
RequestParser::RequestParser()
|
||||
RequestParser::RequestParser(uint maxContentLength)
|
||||
: m_maxContentLength(maxContentLength)
|
||||
{
|
||||
}
|
||||
|
||||
RequestParser::ParseResult RequestParser::parse(const QByteArray &data)
|
||||
RequestParser::ErrorCode RequestParser::parseHttpRequest(const QByteArray &data, Request &request)
|
||||
{
|
||||
// Warning! Header names are converted to lowercase
|
||||
return RequestParser().doParse(data);
|
||||
}
|
||||
m_request = Request();
|
||||
|
||||
RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
|
||||
{
|
||||
// we don't handle malformed requests which use double `LF` as delimiter
|
||||
// Parse HTTP request header
|
||||
const int headerEnd = data.indexOf(EOH);
|
||||
if (headerEnd < 0) {
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return {ParseStatus::Incomplete, Request(), 0};
|
||||
return IncompleteRequest;
|
||||
}
|
||||
|
||||
const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
|
||||
if (!parseStartLines(httpHeaders)) {
|
||||
if (!parseHttpHeader(data.left(headerEnd))) {
|
||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
return BadRequest;
|
||||
}
|
||||
|
||||
const int headerLength = headerEnd + EOH.length();
|
||||
|
||||
// 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) {
|
||||
bool ok = false;
|
||||
const int contentLength = m_request.headers[HEADER_CONTENT_LENGTH].toInt(&ok);
|
||||
if (!ok || (contentLength < 0)) {
|
||||
qWarning() << Q_FUNC_INFO << "bad request: content-length invalid";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
// Parse HTTP request message
|
||||
if (m_request.headers.contains("content-length")) {
|
||||
int contentLength = m_request.headers["content-length"].toInt();
|
||||
if (contentLength < 0) {
|
||||
qWarning() << Q_FUNC_INFO << "bad request: content-length is negative";
|
||||
return BadRequest;
|
||||
}
|
||||
if (contentLength > MAX_CONTENT_SIZE) {
|
||||
|
||||
if (contentLength > static_cast<int>(m_maxContentLength)) {
|
||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
return BadRequest;
|
||||
}
|
||||
|
||||
if (contentLength > 0) {
|
||||
const QByteArray httpBodyView = midView(data, headerLength, contentLength);
|
||||
if (httpBodyView.length() < contentLength) {
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return {ParseStatus::Incomplete, Request(), 0};
|
||||
}
|
||||
|
||||
if (!parsePostMessage(httpBodyView)) {
|
||||
qWarning() << Q_FUNC_INFO << "message body parsing error";
|
||||
return {ParseStatus::BadRequest, Request(), 0};
|
||||
}
|
||||
QByteArray content = data.mid(headerEnd + EOH.length(), contentLength);
|
||||
if (content.length() < contentLength) {
|
||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||
return IncompleteRequest;
|
||||
}
|
||||
|
||||
return {ParseStatus::OK, m_request, (headerLength + contentLength)};
|
||||
if ((contentLength > 0) && !parseContent(content)) {
|
||||
qWarning() << Q_FUNC_INFO << "message parsing error";
|
||||
return BadRequest;
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << Q_FUNC_INFO << "unsupported request method: " << m_request.method;
|
||||
return {ParseStatus::BadRequest, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented"
|
||||
// qDebug() << Q_FUNC_INFO;
|
||||
// qDebug() << "HTTP Request header:";
|
||||
// qDebug() << data.left(headerEnd) << "\n";
|
||||
|
||||
request = m_request;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
bool RequestParser::parseStartLines(const QString &data)
|
||||
bool RequestParser::parseStartingLine(const QString &line)
|
||||
{
|
||||
// we don't handle malformed request which uses `LF` for newline
|
||||
const QVector<QStringRef> lines = data.splitRef(CRLF, QString::SkipEmptyParts);
|
||||
const QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
||||
|
||||
// [rfc7230] 3.2.2. Field Order
|
||||
QStringList requestLines;
|
||||
for (const auto &line : lines) {
|
||||
if (line.at(0).isSpace() && !requestLines.isEmpty()) {
|
||||
// continuation of previous line
|
||||
requestLines.last() += line.toString();
|
||||
}
|
||||
else {
|
||||
requestLines += line.toString();
|
||||
if (rx.indexIn(line.trimmed()) >= 0) {
|
||||
m_request.method = rx.cap(1);
|
||||
|
||||
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
|
||||
m_request.path = url.path(); // Path
|
||||
|
||||
// Parse GET parameters
|
||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
||||
while (i.hasNext()) {
|
||||
QPair<QString, QString> pair = i.next();
|
||||
m_request.gets[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (requestLines.isEmpty())
|
||||
return false;
|
||||
|
||||
if (!parseRequestLine(requestLines[0]))
|
||||
return false;
|
||||
|
||||
for (auto i = ++(requestLines.begin()); i != requestLines.end(); ++i) {
|
||||
if (!parseHeaderLine(*i, m_request.headers))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RequestParser::parseRequestLine(const QString &line)
|
||||
bool RequestParser::parseHeaderLine(const QString &line, QPair<QString, QString> &out)
|
||||
{
|
||||
// [rfc7230] 3.1.1. Request Line
|
||||
|
||||
const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"));
|
||||
const QRegularExpressionMatch match = re.match(line);
|
||||
|
||||
if (!match.hasMatch()) {
|
||||
int i = line.indexOf(QLatin1Char(':'));
|
||||
if (i == -1) {
|
||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request Methods
|
||||
m_request.method = match.captured(1);
|
||||
out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Request Target
|
||||
const QByteArray decodedUrl {QByteArray::fromPercentEncoding(match.captured(2).toLatin1())};
|
||||
const int sepPos = decodedUrl.indexOf('?');
|
||||
m_request.path = QString::fromUtf8(decodedUrl.constData(), (sepPos == -1 ? decodedUrl.size() : sepPos));
|
||||
if (sepPos >= 0)
|
||||
m_request.query = decodedUrl.mid(sepPos + 1);
|
||||
bool RequestParser::parseHttpHeader(const QByteArray &data)
|
||||
{
|
||||
QString str = QString::fromUtf8(data);
|
||||
QStringList lines = str.trimmed().split(EOL);
|
||||
|
||||
// HTTP-version
|
||||
m_request.version = match.captured(3);
|
||||
QStringList headerLines;
|
||||
foreach (const QString &line, lines) {
|
||||
if (line[0].isSpace()) { // header line continuation
|
||||
if (!headerLines.isEmpty()) { // really continuation
|
||||
headerLines.last() += QLatin1Char(' ');
|
||||
headerLines.last() += line.trimmed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
headerLines.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (headerLines.isEmpty())
|
||||
return false; // Empty header
|
||||
|
||||
QStringList::Iterator it = headerLines.begin();
|
||||
if (!parseStartingLine(*it))
|
||||
return false;
|
||||
|
||||
++it;
|
||||
for (; it != headerLines.end(); ++it) {
|
||||
QPair<QString, QString> header;
|
||||
if (!parseHeaderLine(*it, header))
|
||||
return false;
|
||||
|
||||
m_request.headers[header.first] = header.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequestParser::parsePostMessage(const QByteArray &data)
|
||||
QList<QByteArray> RequestParser::splitMultipartData(const QByteArray &data, const QByteArray &boundary)
|
||||
{
|
||||
// parse POST message-body
|
||||
const QString contentType = m_request.headers[HEADER_CONTENT_TYPE];
|
||||
const QString contentTypeLower = contentType.toLower();
|
||||
QList<QByteArray> ret;
|
||||
QByteArray sep = boundary + EOL;
|
||||
const int sepLength = sep.size();
|
||||
|
||||
// application/x-www-form-urlencoded
|
||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) {
|
||||
QListIterator<QStringPair> i(QUrlQuery(data).queryItems(QUrl::FullyDecoded));
|
||||
int start = 0, end = 0;
|
||||
if ((end = data.indexOf(sep, start)) >= 0) {
|
||||
start = end + sepLength; // skip first boundary
|
||||
|
||||
while ((end = data.indexOf(sep, start)) >= 0) {
|
||||
ret << data.mid(start, end - EOL.length() - start);
|
||||
start = end + sepLength;
|
||||
}
|
||||
|
||||
// last or single part
|
||||
sep = boundary + "--" + EOL;
|
||||
if ((end = data.indexOf(sep, start)) >= 0)
|
||||
ret << data.mid(start, end - EOL.length() - start);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RequestParser::parseContent(const QByteArray &data)
|
||||
{
|
||||
// Parse message content
|
||||
qDebug() << Q_FUNC_INFO << "Content-Length: " << m_request.headers["content-length"];
|
||||
qDebug() << Q_FUNC_INFO << "data.size(): " << data.size();
|
||||
|
||||
// Parse url-encoded POST data
|
||||
if (m_request.headers["content-type"].startsWith("application/x-www-form-urlencoded")) {
|
||||
QUrl url;
|
||||
url.setQuery(data);
|
||||
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems(QUrl::FullyDecoded));
|
||||
while (i.hasNext()) {
|
||||
const QStringPair pair = i.next();
|
||||
m_request.posts[pair.first] = pair.second;
|
||||
QPair<QString, QString> pair = i.next();
|
||||
m_request.posts[pair.first.toLower()] = pair.second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// multipart/form-data
|
||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA)) {
|
||||
// [rfc2046] 5.1.1. Common Syntax
|
||||
// Parse multipart/form data (torrent file)
|
||||
/**
|
||||
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
||||
|
||||
// find boundary delimiter
|
||||
const QLatin1String boundaryFieldName("boundary=");
|
||||
const int idx = contentType.indexOf(boundaryFieldName);
|
||||
if (idx < 0) {
|
||||
qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!";
|
||||
return false;
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"Filename\"
|
||||
|
||||
PB020344.torrent
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"torrentfile"; filename=\"PB020344.torrent\"
|
||||
Content-Type: application/x-bittorrent
|
||||
|
||||
BINARY DATA IS HERE
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||
Content-Disposition: form-data; name=\"Upload\"
|
||||
|
||||
Submit Query
|
||||
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5--
|
||||
**/
|
||||
QString contentType = m_request.headers["content-type"];
|
||||
if (contentType.startsWith("multipart/form-data")) {
|
||||
const QRegExp boundaryRegexQuoted("boundary=\"([ \\w'()+,-\\./:=\\?]+)\"");
|
||||
const QRegExp boundaryRegexNotQuoted("boundary=([\\w'()+,-\\./:=\\?]+)");
|
||||
|
||||
QByteArray boundary;
|
||||
if (boundaryRegexQuoted.indexIn(contentType) < 0) {
|
||||
if (boundaryRegexNotQuoted.indexIn(contentType) < 0) {
|
||||
qWarning() << "Could not find boundary in multipart/form-data header!";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
boundary = "--" + boundaryRegexNotQuoted.cap(1).toLatin1();
|
||||
}
|
||||
}
|
||||
else {
|
||||
boundary = "--" + boundaryRegexQuoted.cap(1).toLatin1();
|
||||
}
|
||||
|
||||
const QByteArray delimiter = Utils::String::unquote(contentType.midRef(idx + boundaryFieldName.size())).toLatin1();
|
||||
if (delimiter.isEmpty()) {
|
||||
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Boundary is " << boundary;
|
||||
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
||||
qDebug() << parts.size() << "parts in data";
|
||||
|
||||
// split data by "dash-boundary"
|
||||
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
|
||||
QList<QByteArray> multipart = splitToViews(data, dashDelimiter, QString::SkipEmptyParts);
|
||||
if (multipart.isEmpty()) {
|
||||
qWarning() << Q_FUNC_INFO << "multipart empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove the ending delimiter
|
||||
const QByteArray endDelimiter = QByteArray("--") + delimiter + QByteArray("--") + CRLF;
|
||||
multipart.push_back(viewWithoutEndingWith(multipart.takeLast(), endDelimiter));
|
||||
|
||||
for (const auto &part : multipart) {
|
||||
foreach (const QByteArray& part, parts) {
|
||||
if (!parseFormData(part))
|
||||
return false;
|
||||
}
|
||||
@@ -247,60 +273,71 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << Q_FUNC_INFO << "unknown content type:" << contentType;
|
||||
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RequestParser::parseFormData(const QByteArray &data)
|
||||
{
|
||||
const QList<QByteArray> list = splitToViews(data, EOH, QString::KeepEmptyParts);
|
||||
|
||||
if (list.size() != 2) {
|
||||
qWarning() << Q_FUNC_INFO << "multipart/form-data format error";
|
||||
// Parse form data header
|
||||
const int headerEnd = data.indexOf(EOH);
|
||||
if (headerEnd < 0) {
|
||||
qDebug() << "Invalid form data: \n" << data;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString headers = QString::fromLatin1(list[0]);
|
||||
const QByteArray payload = viewWithoutEndingWith(list[1], CRLF);
|
||||
QString headerStr = QString::fromUtf8(data.left(headerEnd));
|
||||
QStringList lines = headerStr.trimmed().split(EOL);
|
||||
QStringMap headers;
|
||||
foreach (const QString& line, lines) {
|
||||
QPair<QString, QString> header;
|
||||
if (!parseHeaderLine(line, header))
|
||||
return false;
|
||||
|
||||
QStringMap headersMap;
|
||||
const QVector<QStringRef> headerLines = headers.splitRef(CRLF, QString::SkipEmptyParts);
|
||||
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) {
|
||||
const int idx = directive.indexOf('=');
|
||||
if (idx < 0)
|
||||
continue;
|
||||
|
||||
const QString name = directive.left(idx).trimmed().toString().toLower();
|
||||
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
|
||||
headersMap[name] = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!parseHeaderLine(line.toString(), headersMap))
|
||||
return false;
|
||||
}
|
||||
headers[header.first] = header.second;
|
||||
}
|
||||
|
||||
// pick data
|
||||
const QLatin1String filename("filename");
|
||||
const QLatin1String name("name");
|
||||
|
||||
if (headersMap.contains(filename)) {
|
||||
m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload});
|
||||
QStringMap disposition;
|
||||
if (!headers.contains("content-disposition")
|
||||
|| !parseHeaderValue(headers["content-disposition"], disposition)
|
||||
|| !disposition.contains("name")) {
|
||||
qDebug() << "Invalid form data header: \n" << headerStr;
|
||||
return false;
|
||||
}
|
||||
else if (headersMap.contains(name)) {
|
||||
m_request.posts[headersMap[name]] = payload;
|
||||
|
||||
if (disposition.contains("filename")) {
|
||||
UploadedFile ufile;
|
||||
ufile.filename = disposition["filename"];
|
||||
ufile.type = disposition["content-type"];
|
||||
ufile.data = data.mid(headerEnd + EOH.length());
|
||||
|
||||
m_request.files.append(ufile);
|
||||
}
|
||||
else {
|
||||
// malformed
|
||||
qWarning() << Q_FUNC_INFO << "multipart/form-data header error";
|
||||
return false;
|
||||
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(headerEnd + EOH.length()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequestParser::parseHeaderValue(const QString &value, QStringMap &out)
|
||||
{
|
||||
int pos = value.indexOf(QLatin1Char(';'));
|
||||
if (pos == -1) {
|
||||
out[""] = value.trimmed();
|
||||
return true;
|
||||
}
|
||||
|
||||
out[""] = value.left(pos).trimmed();
|
||||
|
||||
QRegExp rx(";\\s*([^=;\"]+)\\s*=\\s*(\"[^\"]*\"|[^\";\\s]+)\\s*");
|
||||
while (rx.indexIn(value, pos) == pos) {
|
||||
out[rx.cap(1).trimmed()] = unquoted(rx.cap(2));
|
||||
pos += rx.cap(0).length();
|
||||
}
|
||||
|
||||
if (pos != value.length())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Ishan Arora and Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
@@ -38,35 +37,32 @@ namespace Http
|
||||
class RequestParser
|
||||
{
|
||||
public:
|
||||
enum class ParseStatus
|
||||
enum ErrorCode
|
||||
{
|
||||
OK,
|
||||
Incomplete,
|
||||
NoError = 0,
|
||||
IncompleteRequest,
|
||||
BadRequest
|
||||
};
|
||||
|
||||
struct ParseResult
|
||||
{
|
||||
// when `status != ParseStatus::OK`, `request` & `frameSize` are undefined
|
||||
ParseStatus status;
|
||||
Request request;
|
||||
long frameSize; // http request frame size (bytes)
|
||||
};
|
||||
|
||||
static ParseResult parse(const QByteArray &data);
|
||||
|
||||
static const long MAX_CONTENT_SIZE = 64 * 1024 * 1024; // 64 MB
|
||||
// when result != NoError parsed request is undefined
|
||||
// Warning! Header names are converted to lower-case.
|
||||
static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */);
|
||||
|
||||
private:
|
||||
RequestParser();
|
||||
RequestParser(uint maxContentLength);
|
||||
|
||||
ParseResult doParse(const QByteArray &data);
|
||||
bool parseStartLines(const QString &data);
|
||||
bool parseRequestLine(const QString &line);
|
||||
ErrorCode parseHttpRequest(const QByteArray &data, Request &request);
|
||||
|
||||
bool parsePostMessage(const QByteArray &data);
|
||||
bool parseHttpHeader(const QByteArray &data);
|
||||
bool parseStartingLine(const QString &line);
|
||||
bool parseContent(const QByteArray &data);
|
||||
bool parseFormData(const QByteArray &data);
|
||||
QList<QByteArray> splitMultipartData(const QByteArray &data, const QByteArray &boundary);
|
||||
|
||||
static bool parseHeaderLine(const QString &line, QPair<QString, QString> &out);
|
||||
static bool parseHeaderValue(const QString &value, QStringMap &out);
|
||||
|
||||
const uint m_maxContentLength;
|
||||
Request m_request;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
|
||||
using namespace Http;
|
||||
|
||||
ResponseBuilder::ResponseBuilder(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ResponseBuilder::status(uint code, const QString &text)
|
||||
{
|
||||
m_response.status = ResponseStatus(code, text);
|
||||
|
||||
@@ -29,13 +29,17 @@
|
||||
#ifndef HTTP_RESPONSEBUILDER_H
|
||||
#define HTTP_RESPONSEBUILDER_H
|
||||
|
||||
#include <QObject>
|
||||
#include "types.h"
|
||||
|
||||
namespace Http
|
||||
{
|
||||
class ResponseBuilder
|
||||
class ResponseBuilder : public QObject
|
||||
{
|
||||
public:
|
||||
explicit ResponseBuilder(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void status(uint code = 200, const QString &text = QLatin1String("OK"));
|
||||
void header(const QString &name, const QString &value);
|
||||
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -38,12 +37,7 @@
|
||||
|
||||
namespace Http
|
||||
{
|
||||
const char METHOD_GET[] = "GET";
|
||||
const char METHOD_POST[] = "POST";
|
||||
|
||||
const char HEADER_CACHE_CONTROL[] = "cache-control";
|
||||
const char HEADER_CONNECTION[] = "connection";
|
||||
const char HEADER_CONTENT_DISPOSITION[] = "content-disposition";
|
||||
const char HEADER_CONTENT_ENCODING[] = "content-encoding";
|
||||
const char HEADER_CONTENT_LENGTH[] = "content-length";
|
||||
const char HEADER_CONTENT_SECURITY_POLICY[] = "content-security-policy";
|
||||
@@ -58,19 +52,16 @@ namespace Http
|
||||
const char HEADER_X_FRAME_OPTIONS[] = "x-frame-options";
|
||||
const char HEADER_X_XSS_PROTECTION[] = "x-xss-protection";
|
||||
|
||||
const char HEADER_REQUEST_METHOD_GET[] = "GET";
|
||||
const char HEADER_REQUEST_METHOD_HEAD[] = "HEAD";
|
||||
const char HEADER_REQUEST_METHOD_POST[] = "POST";
|
||||
|
||||
const char CONTENT_TYPE_HTML[] = "text/html";
|
||||
const char CONTENT_TYPE_JSON[] = "application/json";
|
||||
const char CONTENT_TYPE_CSS[] = "text/css; charset=UTF-8";
|
||||
const char CONTENT_TYPE_GIF[] = "image/gif";
|
||||
const char CONTENT_TYPE_HTML[] = "text/html; charset=UTF-8";
|
||||
const char CONTENT_TYPE_JS[] = "application/javascript; charset=UTF-8";
|
||||
const char CONTENT_TYPE_JSON[] = "application/json";
|
||||
const char CONTENT_TYPE_PNG[] = "image/png";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain";
|
||||
const char CONTENT_TYPE_FORM_ENCODED[] = "application/x-www-form-urlencoded";
|
||||
const char CONTENT_TYPE_FORM_DATA[] = "multipart/form-data";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain; charset=UTF-8";
|
||||
const char CONTENT_TYPE_SVG[] = "image/svg+xml";
|
||||
|
||||
// portability: "\r\n" doesn't guarantee mapping to the correct symbol
|
||||
// portability: "\r\n" doesn't guarantee mapping to the correct value
|
||||
const char CRLF[] = {0x0D, 0x0A, '\0'};
|
||||
|
||||
struct Environment
|
||||
@@ -84,18 +75,17 @@ namespace Http
|
||||
|
||||
struct UploadedFile
|
||||
{
|
||||
QString filename;
|
||||
QString type; // MIME type
|
||||
QByteArray data;
|
||||
QString filename; // original filename
|
||||
QString type; // MIME type
|
||||
QByteArray data; // File data
|
||||
};
|
||||
|
||||
struct Request
|
||||
{
|
||||
QString version;
|
||||
QString method;
|
||||
QString path;
|
||||
QByteArray query;
|
||||
QStringMap headers;
|
||||
QStringMap gets;
|
||||
QStringMap posts;
|
||||
QVector<UploadedFile> files;
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ void IconProvider::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,4 @@ QString IconProvider::getIconPath(const QString &iconId)
|
||||
return ":/icons/qbt-theme/" + iconId + ".png";
|
||||
}
|
||||
|
||||
IconProvider *IconProvider::m_instance = nullptr;
|
||||
IconProvider *IconProvider::m_instance = 0;
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
virtual QString getIconPath(const QString &iconId);
|
||||
|
||||
protected:
|
||||
explicit IconProvider(QObject *parent = nullptr);
|
||||
explicit IconProvider(QObject *parent = 0);
|
||||
~IconProvider();
|
||||
|
||||
static IconProvider *m_instance;
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include <QDateTime>
|
||||
#include "base/utils/string.h"
|
||||
|
||||
Logger *Logger::m_instance = nullptr;
|
||||
Logger* Logger::m_instance = 0;
|
||||
|
||||
Logger::Logger()
|
||||
: m_lock(QReadWriteLock::Recursive)
|
||||
, m_msgCounter(0)
|
||||
, m_peerCounter(0)
|
||||
: lock(QReadWriteLock::Recursive)
|
||||
, msgCounter(0)
|
||||
, peerCounter(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@ void Logger::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
||||
{
|
||||
QWriteLocker locker(&m_lock);
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Msg temp = {m_msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message.toHtmlEscaped()};
|
||||
Log::Msg temp = { msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message.toHtmlEscaped() };
|
||||
m_messages.push_back(temp);
|
||||
|
||||
if (m_messages.size() >= MAX_LOG_MESSAGES)
|
||||
@@ -48,9 +48,9 @@ void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
||||
|
||||
void Logger::addPeer(const QString &ip, bool blocked, const QString &reason)
|
||||
{
|
||||
QWriteLocker locker(&m_lock);
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Peer temp = {m_peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip.toHtmlEscaped(), blocked, reason.toHtmlEscaped()};
|
||||
Log::Peer temp = { peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip.toHtmlEscaped(), blocked, reason.toHtmlEscaped() };
|
||||
m_peers.push_back(temp);
|
||||
|
||||
if (m_peers.size() >= MAX_LOG_MESSAGES)
|
||||
@@ -61,9 +61,9 @@ void Logger::addPeer(const QString &ip, bool blocked, const QString &reason)
|
||||
|
||||
QVector<Log::Msg> Logger::getMessages(int lastKnownId) const
|
||||
{
|
||||
QReadLocker locker(&m_lock);
|
||||
QReadLocker locker(&lock);
|
||||
|
||||
int diff = m_msgCounter - lastKnownId - 1;
|
||||
int diff = msgCounter - lastKnownId - 1;
|
||||
int size = m_messages.size();
|
||||
|
||||
if ((lastKnownId == -1) || (diff >= size))
|
||||
@@ -77,9 +77,9 @@ QVector<Log::Msg> Logger::getMessages(int lastKnownId) const
|
||||
|
||||
QVector<Log::Peer> Logger::getPeers(int lastKnownId) const
|
||||
{
|
||||
QReadLocker locker(&m_lock);
|
||||
QReadLocker locker(&lock);
|
||||
|
||||
int diff = m_peerCounter - lastKnownId - 1;
|
||||
int diff = peerCounter - lastKnownId - 1;
|
||||
int size = m_peers.size();
|
||||
|
||||
if ((lastKnownId == -1) || (diff >= size))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QReadWriteLock>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QReadWriteLock>
|
||||
#include <QObject>
|
||||
|
||||
const int MAX_LOG_MESSAGES = 20000;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Log
|
||||
NORMAL = 0x1,
|
||||
INFO = 0x2,
|
||||
WARNING = 0x4,
|
||||
CRITICAL = 0x8 // ERROR is defined by libtorrent and results in compiler error
|
||||
CRITICAL = 0x8 //ERROR is defined by libtorrent and results in compiler error
|
||||
};
|
||||
Q_DECLARE_FLAGS(MsgTypes, MsgType)
|
||||
|
||||
@@ -63,12 +63,12 @@ private:
|
||||
Logger();
|
||||
~Logger();
|
||||
|
||||
static Logger *m_instance;
|
||||
static Logger* m_instance;
|
||||
QVector<Log::Msg> m_messages;
|
||||
QVector<Log::Peer> m_peers;
|
||||
mutable QReadWriteLock m_lock;
|
||||
int m_msgCounter;
|
||||
int m_peerCounter;
|
||||
mutable QReadWriteLock lock;
|
||||
int msgCounter;
|
||||
int peerCounter;
|
||||
};
|
||||
|
||||
// Helper function
|
||||
|
||||
@@ -52,7 +52,7 @@ DNSUpdater::DNSUpdater(QObject *parent)
|
||||
|
||||
// Start IP checking timer
|
||||
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
|
||||
connect(&m_ipCheckTimer, &QTimer::timeout, this, &DNSUpdater::checkPublicIP);
|
||||
connect(&m_ipCheckTimer, SIGNAL(timeout()), SLOT(checkPublicIP()));
|
||||
m_ipCheckTimer.start();
|
||||
|
||||
// Check lastUpdate to avoid flooding
|
||||
@@ -77,9 +77,8 @@ void DNSUpdater::checkPublicIP()
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
"http://checkip.dyndns.org", false, 0, false,
|
||||
"qBittorrent/" QBT_VERSION_2);
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &DNSUpdater::ipRequestFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipRequestFailed);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipRequestFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipRequestFailed(QString, QString)));
|
||||
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
}
|
||||
@@ -125,9 +124,8 @@ void DNSUpdater::updateDNSService()
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
getUpdateUrl(), false, 0, false,
|
||||
"qBittorrent/" QBT_VERSION_2);
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &DNSUpdater::ipUpdateFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipUpdateFailed);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipUpdateFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipUpdateFailed(QString, QString)));
|
||||
}
|
||||
|
||||
QString DNSUpdater::getUpdateUrl() const
|
||||
@@ -198,7 +196,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything below is an error, stop updating until the user updates something
|
||||
// Everything bellow is an error, stop updating until the user updates something
|
||||
m_ipCheckTimer.stop();
|
||||
m_lastIP.clear();
|
||||
if (code == "nohost") {
|
||||
|
||||
@@ -121,15 +121,15 @@ void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
|
||||
// Total number of bytes is available
|
||||
if (bytesTotal > m_sizeLimit) {
|
||||
m_reply->abort();
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal)).arg(Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
}
|
||||
else {
|
||||
disconnect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
||||
disconnect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(checkDownloadSize(qint64, qint64)));
|
||||
}
|
||||
}
|
||||
else if (bytesReceived > m_sizeLimit) {
|
||||
m_reply->abort();
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived)).arg(Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ void DownloadHandler::init()
|
||||
{
|
||||
m_reply->setParent(this);
|
||||
if (m_sizeLimit > 0)
|
||||
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
||||
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
|
||||
connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(checkDownloadSize(qint64, qint64)));
|
||||
connect(m_reply, SIGNAL(finished()), this, SLOT(processFinishedDownload()));
|
||||
}
|
||||
|
||||
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
|
||||
|
||||
@@ -48,10 +48,10 @@ const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/
|
||||
|
||||
namespace
|
||||
{
|
||||
class NetworkCookieJar : public QNetworkCookieJar
|
||||
class NetworkCookieJar: public QNetworkCookieJar
|
||||
{
|
||||
public:
|
||||
explicit NetworkCookieJar(QObject *parent = nullptr)
|
||||
explicit NetworkCookieJar(QObject *parent = 0)
|
||||
: QNetworkCookieJar(parent)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
@@ -107,13 +107,13 @@ namespace
|
||||
|
||||
using namespace Net;
|
||||
|
||||
DownloadManager *DownloadManager::m_instance = nullptr;
|
||||
DownloadManager *DownloadManager::m_instance = 0;
|
||||
|
||||
DownloadManager::DownloadManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#ifndef QT_NO_OPENSSL
|
||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Net::DownloadManager::ignoreSslErrors);
|
||||
connect(&m_networkManager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)), this, SLOT(ignoreSslErrors(QNetworkReply *, QList<QSslError>)));
|
||||
#endif
|
||||
m_networkManager.setCookieJar(new NetworkCookieJar(this));
|
||||
}
|
||||
@@ -128,7 +128,7 @@ void DownloadManager::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Net
|
||||
#endif
|
||||
|
||||
private:
|
||||
explicit DownloadManager(QObject *parent = nullptr);
|
||||
explicit DownloadManager(QObject *parent = 0);
|
||||
|
||||
void applyProxySettings();
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ GeoIPManager::GeoIPManager()
|
||||
, m_geoIPDatabase(nullptr)
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &GeoIPManager::configure);
|
||||
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
|
||||
}
|
||||
|
||||
GeoIPManager::~GeoIPManager()
|
||||
@@ -96,14 +96,15 @@ void GeoIPManager::loadDatabase()
|
||||
}
|
||||
|
||||
QString filepath = Utils::Fs::expandPathAbs(
|
||||
QString("%1%2/%3").arg(specialFolderLocation(SpecialFolder::Data), GEOIP_FOLDER, GEOIP_FILENAME));
|
||||
QString("%1%2/%3").arg(specialFolderLocation(SpecialFolder::Data))
|
||||
.arg(GEOIP_FOLDER).arg(GEOIP_FILENAME));
|
||||
|
||||
QString error;
|
||||
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
|
||||
if (m_geoIPDatabase)
|
||||
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
||||
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
|
||||
Log::INFO);
|
||||
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
|
||||
Log::INFO);
|
||||
else
|
||||
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
|
||||
|
||||
@@ -119,9 +120,8 @@ void GeoIPManager::manageDatabaseUpdate()
|
||||
void GeoIPManager::downloadDatabaseFile()
|
||||
{
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &GeoIPManager::downloadFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &GeoIPManager::downloadFailed);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(downloadFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(downloadFailed(QString, QString)));
|
||||
}
|
||||
|
||||
QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
|
||||
@@ -432,13 +432,13 @@ void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
|
||||
delete m_geoIPDatabase;
|
||||
m_geoIPDatabase = geoIPDatabase;
|
||||
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
||||
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
|
||||
Log::INFO);
|
||||
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
|
||||
Log::INFO);
|
||||
QString targetPath = Utils::Fs::expandPathAbs(
|
||||
specialFolderLocation(SpecialFolder::Data) + GEOIP_FOLDER);
|
||||
if (!QDir(targetPath).exists())
|
||||
QDir().mkpath(targetPath);
|
||||
QFile targetFile(QString("%1/%2").arg(targetPath, GEOIP_FILENAME));
|
||||
QFile targetFile(QString("%1/%2").arg(targetPath).arg(GEOIP_FILENAME));
|
||||
if (!targetFile.open(QFile::WriteOnly) || (targetFile.write(data) == -1)) {
|
||||
Logger::instance()->addMessage(
|
||||
tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "base/logger.h"
|
||||
#include "base/settingsstorage.h"
|
||||
|
||||
static const QString KEY_ENABLED = QStringLiteral("Network/PortForwardingEnabled");
|
||||
static const QString KEY_ENABLED = QLatin1String("Network/PortForwardingEnabled");
|
||||
|
||||
namespace libt = libtorrent;
|
||||
using namespace Net;
|
||||
@@ -121,10 +121,8 @@ void PortForwarder::start()
|
||||
settingsPack.set_bool(libt::settings_pack::enable_natpmp, true);
|
||||
m_provider->apply_settings(settingsPack);
|
||||
#endif
|
||||
for (auto i = m_mappedPorts.begin(); i != m_mappedPorts.end(); ++i) {
|
||||
// quint16 port = i.key();
|
||||
i.value() = m_provider->add_port_mapping(libt::session::tcp, i.key(), i.key());
|
||||
}
|
||||
foreach (quint16 port, m_mappedPorts.keys())
|
||||
m_mappedPorts[port] = m_provider->add_port_mapping(libt::session::tcp, port, port);
|
||||
m_active = true;
|
||||
Logger::instance()->addMessage(tr("UPnP / NAT-PMP support [ON]"), Log::INFO);
|
||||
}
|
||||
|
||||
@@ -26,23 +26,25 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QVariant>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
|
||||
#include "base/types.h"
|
||||
#include "geoipdatabase.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const quint32 __ENDIAN_TEST__ = 0x00000001;
|
||||
const bool __IS_LITTLE_ENDIAN__ = (reinterpret_cast<const uchar *>(&__ENDIAN_TEST__)[0] == 0x01);
|
||||
const qint32 MAX_FILE_SIZE = 67108864; // 64MB
|
||||
const char DB_TYPE[] = "GeoLite2-Country";
|
||||
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
|
||||
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
|
||||
const char DATA_SECTION_SEPARATOR[16] = {0};
|
||||
const char DATA_SECTION_SEPARATOR[16] = { 0 };
|
||||
|
||||
enum class DataType
|
||||
{
|
||||
@@ -89,7 +91,7 @@ GeoIPDatabase::GeoIPDatabase(quint32 size)
|
||||
|
||||
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
{
|
||||
GeoIPDatabase *db = nullptr;
|
||||
GeoIPDatabase *db = 0;
|
||||
QFile file(filename);
|
||||
if (file.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
@@ -120,7 +122,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
|
||||
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
{
|
||||
GeoIPDatabase *db = nullptr;
|
||||
GeoIPDatabase *db = 0;
|
||||
if (data.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
return 0;
|
||||
@@ -446,12 +448,8 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
|
||||
|
||||
void GeoIPDatabase::fromBigEndian(uchar *buf, quint32 len) const
|
||||
{
|
||||
#if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN)
|
||||
std::reverse(buf, buf + len);
|
||||
#else
|
||||
Q_UNUSED(buf);
|
||||
Q_UNUSED(len);
|
||||
#endif
|
||||
if (__IS_LITTLE_ENDIAN__)
|
||||
std::reverse(buf, buf + len);
|
||||
}
|
||||
|
||||
QVariant GeoIPDatabase::readMapValue(quint32 &offset, quint32 count) const
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
#ifndef GEOIPDATABASE_H
|
||||
#define GEOIPDATABASE_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
|
||||
class QByteArray;
|
||||
class QDateTime;
|
||||
class QHostAddress;
|
||||
class QString;
|
||||
class QByteArray;
|
||||
class QDateTime;
|
||||
|
||||
struct DataFieldDescriptor;
|
||||
|
||||
|
||||
@@ -27,10 +27,9 @@
|
||||
*/
|
||||
|
||||
#include "proxyconfigurationmanager.h"
|
||||
|
||||
#include "base/settingsstorage.h"
|
||||
|
||||
#define SETTINGS_KEY(name) QStringLiteral("Network/Proxy/" name)
|
||||
#define SETTINGS_KEY(name) "Network/Proxy/" name
|
||||
const QString KEY_ONLY_FOR_TORRENTS = SETTINGS_KEY("OnlyForTorrents");
|
||||
const QString KEY_TYPE = SETTINGS_KEY("Type");
|
||||
const QString KEY_IP = SETTINGS_KEY("IP");
|
||||
@@ -81,7 +80,7 @@ void ProxyConfigurationManager::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,18 +135,18 @@ void ProxyConfigurationManager::configureProxy()
|
||||
if (!m_isProxyOnlyForTorrents) {
|
||||
switch (m_config.type) {
|
||||
case ProxyType::HTTP_PW:
|
||||
proxyStrHTTP = QString("http://%1:%2@%3:%4").arg(m_config.username
|
||||
, m_config.password, m_config.ip, QString::number(m_config.port));
|
||||
proxyStrHTTP = QString("http://%1:%2@%3:%4").arg(m_config.username)
|
||||
.arg(m_config.password).arg(m_config.ip).arg(m_config.port);
|
||||
break;
|
||||
case ProxyType::HTTP:
|
||||
proxyStrHTTP = QString("http://%1:%2").arg(m_config.ip, QString::number(m_config.port));
|
||||
proxyStrHTTP = QString("http://%1:%2").arg(m_config.ip).arg(m_config.port);
|
||||
break;
|
||||
case ProxyType::SOCKS5:
|
||||
proxyStrSOCK = QString("%1:%2").arg(m_config.ip, QString::number(m_config.port));
|
||||
proxyStrSOCK = QString("%1:%2").arg(m_config.ip).arg(m_config.port);
|
||||
break;
|
||||
case ProxyType::SOCKS5_PW:
|
||||
proxyStrSOCK = QString("%1:%2@%3:%4").arg(m_config.username
|
||||
, m_config.password, m_config.ip, QString::number(m_config.port));
|
||||
proxyStrSOCK = QString("%1:%2@%3:%4").arg(m_config.username)
|
||||
.arg(m_config.password).arg(m_config.ip).arg(m_config.port);
|
||||
break;
|
||||
default:
|
||||
qDebug("Disabling HTTP communications proxy");
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Net
|
||||
QString password;
|
||||
};
|
||||
|
||||
class ProxyConfigurationManager : public QObject
|
||||
class ProxyConfigurationManager: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ProxyConfigurationManager)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,24 +24,26 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "reverseresolution.h"
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHostInfo>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/version.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include "reverseresolution.h"
|
||||
|
||||
const int CACHE_SIZE = 500;
|
||||
|
||||
using namespace Net;
|
||||
|
||||
static inline bool isUsefulHostName(const QString &hostname, const QString &ip)
|
||||
{
|
||||
return (!hostname.isEmpty() && (hostname != ip));
|
||||
return (!hostname.isEmpty() && hostname != ip);
|
||||
}
|
||||
|
||||
ReverseResolution::ReverseResolution(QObject *parent)
|
||||
@@ -65,11 +67,7 @@ void ReverseResolution::resolve(const QString &ip)
|
||||
}
|
||||
else {
|
||||
// Actually resolve the ip
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
m_lookups.insert(QHostInfo::lookupHost(ip, this, &ReverseResolution::hostResolved), ip);
|
||||
#else
|
||||
m_lookups.insert(QHostInfo::lookupHost(ip, this, SLOT(hostResolved(QHostInfo))), ip);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,6 +24,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef NET_REVERSERESOLUTION_H
|
||||
@@ -32,8 +34,10 @@
|
||||
#include <QCache>
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QHostInfo;
|
||||
class QString;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Net
|
||||
{
|
||||
@@ -43,7 +47,7 @@ namespace Net
|
||||
Q_DISABLE_COPY(ReverseResolution)
|
||||
|
||||
public:
|
||||
explicit ReverseResolution(QObject *parent = nullptr);
|
||||
explicit ReverseResolution(QObject *parent = 0);
|
||||
~ReverseResolution();
|
||||
|
||||
void resolve(const QString &ip);
|
||||
|
||||
@@ -111,10 +111,9 @@ Smtp::Smtp(QObject *parent)
|
||||
m_socket = new QTcpSocket(this);
|
||||
#endif
|
||||
|
||||
connect(m_socket, &QIODevice::readyRead, this, &Smtp::readyRead);
|
||||
connect(m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
||||
connect(m_socket, static_cast<void (QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error)
|
||||
, this, &Smtp::error);
|
||||
connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
|
||||
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
||||
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(error(QAbstractSocket::SocketError)));
|
||||
|
||||
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
|
||||
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
|
||||
@@ -130,8 +129,8 @@ Smtp::~Smtp()
|
||||
|
||||
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
|
||||
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
||||
+ encodeMimeHeader("From", from, latin1)
|
||||
+ encodeMimeHeader("Subject", subject, latin1)
|
||||
@@ -141,8 +140,8 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n";
|
||||
// Encode the body in base64
|
||||
QString crlfBody = body;
|
||||
QByteArray b = crlfBody.replace("\n", "\r\n").toUtf8().toBase64();
|
||||
QString crlf_body = body;
|
||||
QByteArray b = crlf_body.replace("\n", "\r\n").toUtf8().toBase64();
|
||||
int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
m_message += b.mid(i, 78);
|
||||
@@ -165,7 +164,7 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
||||
m_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||
m_useSsl = false;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -184,7 +183,7 @@ void Smtp::readyRead()
|
||||
QByteArray code = line.left(3);
|
||||
|
||||
switch (m_state) {
|
||||
case Init:
|
||||
case Init: {
|
||||
if (code[0] == '2') {
|
||||
// The server may send a multiline greeting/INIT/220 response.
|
||||
// We wait until it finishes.
|
||||
@@ -198,6 +197,7 @@ void Smtp::readyRead()
|
||||
m_state = Close;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EhloSent:
|
||||
case HeloSent:
|
||||
case EhloGreetReceived:
|
||||
@@ -447,7 +447,7 @@ void Smtp::startTLS()
|
||||
#endif
|
||||
}
|
||||
|
||||
void Smtp::authCramMD5(const QByteArray &challenge)
|
||||
void Smtp::authCramMD5(const QByteArray& challenge)
|
||||
{
|
||||
if (m_state != AuthRequestSent) {
|
||||
m_socket->write("auth cram-md5\r\n");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2014 sledgehammer999
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -25,6 +25,9 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
* Contact : hammered999@gmail.com
|
||||
*/
|
||||
|
||||
#include "preferences.h"
|
||||
@@ -56,7 +59,7 @@
|
||||
#include "utils/fs.h"
|
||||
#include "utils/misc.h"
|
||||
|
||||
Preferences *Preferences::m_instance = nullptr;
|
||||
Preferences *Preferences::m_instance = 0;
|
||||
|
||||
Preferences::Preferences() = default;
|
||||
|
||||
@@ -75,7 +78,7 @@ void Preferences::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,12 +420,12 @@ void Preferences::setSchedulerEndTime(const QTime &time)
|
||||
setValue("Preferences/Scheduler/end_time", time);
|
||||
}
|
||||
|
||||
SchedulerDays Preferences::getSchedulerDays() const
|
||||
scheduler_days Preferences::getSchedulerDays() const
|
||||
{
|
||||
return static_cast<SchedulerDays>(value("Preferences/Scheduler/days", EVERY_DAY).toInt());
|
||||
return static_cast<scheduler_days>(value("Preferences/Scheduler/days", EVERY_DAY).toInt());
|
||||
}
|
||||
|
||||
void Preferences::setSchedulerDays(SchedulerDays days)
|
||||
void Preferences::setSchedulerDays(scheduler_days days)
|
||||
{
|
||||
setValue("Preferences/Scheduler/days", static_cast<int>(days));
|
||||
}
|
||||
@@ -606,26 +609,6 @@ void Preferences::setWebUiHttpsKey(const QByteArray &data)
|
||||
setValue("Preferences/WebUI/HTTPS/Key", data);
|
||||
}
|
||||
|
||||
bool Preferences::isAltWebUiEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/AlternativeUIEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setAltWebUiEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/WebUI/AlternativeUIEnabled", enabled);
|
||||
}
|
||||
|
||||
QString Preferences::getWebUiRootFolder() const
|
||||
{
|
||||
return value("Preferences/WebUI/RootFolder").toString();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiRootFolder(const QString &path)
|
||||
{
|
||||
setValue("Preferences/WebUI/RootFolder", path);
|
||||
}
|
||||
|
||||
bool Preferences::isDynDNSEnabled() const
|
||||
{
|
||||
return value("Preferences/DynDNS/Enabled", false).toBool();
|
||||
@@ -687,12 +670,12 @@ QString Preferences::getUILockPasswordMD5() const
|
||||
return value("Locking/password").toString();
|
||||
}
|
||||
|
||||
void Preferences::setUILockPassword(const QString &clearPassword)
|
||||
void Preferences::setUILockPassword(const QString &clear_password)
|
||||
{
|
||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||
md5.addData(clearPassword.toLocal8Bit());
|
||||
QString md5Password = md5.result().toHex();
|
||||
setValue("Locking/password", md5Password);
|
||||
md5.addData(clear_password.toLocal8Bit());
|
||||
QString md5_password = md5.result().toHex();
|
||||
setValue("Locking/password", md5_password);
|
||||
}
|
||||
|
||||
bool Preferences::isUILocked() const
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
* Bittorrent Client using Qt4 and libtorrent.
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2014 sledgehammer999
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -25,6 +25,9 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
* Contact : hammered999@gmail.com
|
||||
*/
|
||||
|
||||
#ifndef PREFERENCES_H
|
||||
@@ -44,7 +47,7 @@
|
||||
#include "base/utils/net.h"
|
||||
#include "types.h"
|
||||
|
||||
enum SchedulerDays
|
||||
enum scheduler_days
|
||||
{
|
||||
EVERY_DAY,
|
||||
WEEK_DAYS,
|
||||
@@ -80,7 +83,7 @@ namespace DNS
|
||||
|
||||
class SettingsStorage;
|
||||
|
||||
class Preferences : public QObject
|
||||
class Preferences: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Preferences)
|
||||
@@ -163,8 +166,8 @@ public:
|
||||
void setSchedulerStartTime(const QTime &time);
|
||||
QTime getSchedulerEndTime() const;
|
||||
void setSchedulerEndTime(const QTime &time);
|
||||
SchedulerDays getSchedulerDays() const;
|
||||
void setSchedulerDays(SchedulerDays days);
|
||||
scheduler_days getSchedulerDays() const;
|
||||
void setSchedulerDays(scheduler_days days);
|
||||
|
||||
// Search
|
||||
bool isSearchEnabled() const;
|
||||
@@ -201,10 +204,6 @@ public:
|
||||
void setWebUiHttpsCertificate(const QByteArray &data);
|
||||
QByteArray getWebUiHttpsKey() const;
|
||||
void setWebUiHttpsKey(const QByteArray &data);
|
||||
bool isAltWebUiEnabled() const;
|
||||
void setAltWebUiEnabled(bool enabled);
|
||||
QString getWebUiRootFolder() const;
|
||||
void setWebUiRootFolder(const QString &path);
|
||||
|
||||
// Dynamic DNS
|
||||
bool isDynDNSEnabled() const;
|
||||
@@ -219,7 +218,7 @@ public:
|
||||
void setDynDNSPassword(const QString &password);
|
||||
|
||||
// Advanced settings
|
||||
void setUILockPassword(const QString &clearPassword);
|
||||
void setUILockPassword(const QString &clear_password);
|
||||
void clearUILockPassword();
|
||||
QString getUILockPasswordMD5() const;
|
||||
bool isUILocked() const;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QBT_PROFILE_P_H
|
||||
@@ -32,7 +33,6 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "base/profile.h"
|
||||
|
||||
namespace Private
|
||||
@@ -63,7 +63,7 @@ namespace Private
|
||||
};
|
||||
|
||||
/// Default implementation. Takes paths from system
|
||||
class DefaultProfile : public Profile
|
||||
class DefaultProfile: public Profile
|
||||
{
|
||||
public:
|
||||
DefaultProfile(const QString &configurationName);
|
||||
@@ -86,7 +86,7 @@ namespace Private
|
||||
};
|
||||
|
||||
/// Custom tree: creates directories under the specified root directory
|
||||
class CustomProfile : public Profile
|
||||
class CustomProfile: public Profile
|
||||
{
|
||||
public:
|
||||
CustomProfile(const QString &rootPath, const QString &configurationName);
|
||||
@@ -114,14 +114,14 @@ namespace Private
|
||||
virtual ~PathConverter() = default;
|
||||
};
|
||||
|
||||
class NoConvertConverter : public PathConverter
|
||||
class NoConvertConverter: public PathConverter
|
||||
{
|
||||
public:
|
||||
QString toPortablePath(const QString &path) const override;
|
||||
QString fromPortablePath(const QString &portablePath) const override;
|
||||
};
|
||||
|
||||
class Converter : public PathConverter
|
||||
class Converter: public PathConverter
|
||||
{
|
||||
public:
|
||||
Converter(const QString &basePath);
|
||||
@@ -132,5 +132,4 @@ namespace Private
|
||||
QDir m_baseDir;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // QBT_PROFILE_P_H
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "profile.h"
|
||||
@@ -42,6 +43,7 @@ Profile::Profile(Private::Profile *impl, Private::PathConverter *pathConverter)
|
||||
ensureDirectoryExists(SpecialFolder::Cache);
|
||||
ensureDirectoryExists(SpecialFolder::Config);
|
||||
ensureDirectoryExists(SpecialFolder::Data);
|
||||
ensureDirectoryExists(SpecialFolder::Downloads);
|
||||
}
|
||||
|
||||
// to generate correct call to ProfilePrivate::~ProfileImpl()
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
#include <QScopedPointer>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
|
||||
class Application;
|
||||
|
||||
|
||||
@@ -25,339 +25,24 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "rss_parser.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QGlobalStatic>
|
||||
#include <QHash>
|
||||
#include <QDateTime>
|
||||
#include <QMetaObject>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QXmlStreamEntityResolver>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "../rss_article.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// (X)HTML entities declared in:
|
||||
// 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
|
||||
using StringHash = QHash<QString, QString>;
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(StringHash, HTML_ENTITIES, ({
|
||||
{"nbsp", " "}, // no-break space = non-breaking space, U+00A0 ISOnum
|
||||
{"iexcl", "¡"}, // inverted exclamation mark, U+00A1 ISOnum
|
||||
{"cent", "¢"}, // cent sign, U+00A2 ISOnum
|
||||
{"pound", "£"}, // pound sign, U+00A3 ISOnum
|
||||
{"curren", "¤"}, // currency sign, U+00A4 ISOnum
|
||||
{"yen", "¥"}, // yen sign = yuan sign, U+00A5 ISOnum
|
||||
{"brvbar", "¦"}, // broken bar = broken vertical bar, U+00A6 ISOnum
|
||||
{"sect", "§"}, // section sign, U+00A7 ISOnum
|
||||
{"uml", "¨"}, // diaeresis = spacing diaeresis, U+00A8 ISOdia
|
||||
{"copy", "©"}, // copyright sign, U+00A9 ISOnum
|
||||
{"ordf", "ª"}, // feminine ordinal indicator, U+00AA ISOnum
|
||||
{"laquo", "«"}, // left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum
|
||||
{"not", "¬"}, // not sign = angled dash, U+00AC ISOnum
|
||||
{"shy", "­"}, // soft hyphen = discretionary hyphen, U+00AD ISOnum
|
||||
{"reg", "®"}, // registered sign = registered trade mark sign, U+00AE ISOnum
|
||||
{"macr", "¯"}, // macron = spacing macron = overline = APL overbar, U+00AF ISOdia
|
||||
{"deg", "°"}, // degree sign, U+00B0 ISOnum
|
||||
{"plusmn", "±"}, // plus-minus sign = plus-or-minus sign, U+00B1 ISOnum
|
||||
{"sup2", "²"}, // superscript two = superscript digit two = squared, U+00B2 ISOnum
|
||||
{"sup3", "³"}, // superscript three = superscript digit three = cubed, U+00B3 ISOnum
|
||||
{"acute", "´"}, // acute accent = spacing acute, U+00B4 ISOdia
|
||||
{"micro", "µ"}, // micro sign, U+00B5 ISOnum
|
||||
{"para", "¶"}, // pilcrow sign = paragraph sign, U+00B6 ISOnum
|
||||
{"middot", "·"}, // middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum
|
||||
{"cedil", "¸"}, // cedilla = spacing cedilla, U+00B8 ISOdia
|
||||
{"sup1", "¹"}, // superscript one = superscript digit one, U+00B9 ISOnum
|
||||
{"ordm", "º"}, // masculine ordinal indicator, U+00BA ISOnum
|
||||
{"raquo", "»"}, // right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum
|
||||
{"frac14", "¼"}, // vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum
|
||||
{"frac12", "½"}, // vulgar fraction one half = fraction one half, U+00BD ISOnum
|
||||
{"frac34", "¾"}, // vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum
|
||||
{"iquest", "¿"}, // inverted question mark = turned question mark, U+00BF ISOnum
|
||||
{"Agrave", "À"}, // latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1
|
||||
{"Aacute", "Á"}, // latin capital letter A with acute, U+00C1 ISOlat1
|
||||
{"Acirc", "Â"}, // latin capital letter A with circumflex, U+00C2 ISOlat1
|
||||
{"Atilde", "Ã"}, // latin capital letter A with tilde, U+00C3 ISOlat1
|
||||
{"Auml", "Ä"}, // latin capital letter A with diaeresis, U+00C4 ISOlat1
|
||||
{"Aring", "Å"}, // latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1
|
||||
{"AElig", "Æ"}, // latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1
|
||||
{"Ccedil", "Ç"}, // latin capital letter C with cedilla, U+00C7 ISOlat1
|
||||
{"Egrave", "È"}, // latin capital letter E with grave, U+00C8 ISOlat1
|
||||
{"Eacute", "É"}, // latin capital letter E with acute, U+00C9 ISOlat1
|
||||
{"Ecirc", "Ê"}, // latin capital letter E with circumflex, U+00CA ISOlat1
|
||||
{"Euml", "Ë"}, // latin capital letter E with diaeresis, U+00CB ISOlat1
|
||||
{"Igrave", "Ì"}, // latin capital letter I with grave, U+00CC ISOlat1
|
||||
{"Iacute", "Í"}, // latin capital letter I with acute, U+00CD ISOlat1
|
||||
{"Icirc", "Î"}, // latin capital letter I with circumflex, U+00CE ISOlat1
|
||||
{"Iuml", "Ï"}, // latin capital letter I with diaeresis, U+00CF ISOlat1
|
||||
{"ETH", "Ð"}, // latin capital letter ETH, U+00D0 ISOlat1
|
||||
{"Ntilde", "Ñ"}, // latin capital letter N with tilde, U+00D1 ISOlat1
|
||||
{"Ograve", "Ò"}, // latin capital letter O with grave, U+00D2 ISOlat1
|
||||
{"Oacute", "Ó"}, // latin capital letter O with acute, U+00D3 ISOlat1
|
||||
{"Ocirc", "Ô"}, // latin capital letter O with circumflex, U+00D4 ISOlat1
|
||||
{"Otilde", "Õ"}, // latin capital letter O with tilde, U+00D5 ISOlat1
|
||||
{"Ouml", "Ö"}, // latin capital letter O with diaeresis, U+00D6 ISOlat1
|
||||
{"times", "×"}, // multiplication sign, U+00D7 ISOnum
|
||||
{"Oslash", "Ø"}, // latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1
|
||||
{"Ugrave", "Ù"}, // latin capital letter U with grave, U+00D9 ISOlat1
|
||||
{"Uacute", "Ú"}, // latin capital letter U with acute, U+00DA ISOlat1
|
||||
{"Ucirc", "Û"}, // latin capital letter U with circumflex, U+00DB ISOlat1
|
||||
{"Uuml", "Ü"}, // latin capital letter U with diaeresis, U+00DC ISOlat1
|
||||
{"Yacute", "Ý"}, // latin capital letter Y with acute, U+00DD ISOlat1
|
||||
{"THORN", "Þ"}, // latin capital letter THORN, U+00DE ISOlat1
|
||||
{"szlig", "ß"}, // latin small letter sharp s = ess-zed, U+00DF ISOlat1
|
||||
{"agrave", "à"}, // latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1
|
||||
{"aacute", "á"}, // latin small letter a with acute, U+00E1 ISOlat1
|
||||
{"acirc", "â"}, // latin small letter a with circumflex, U+00E2 ISOlat1
|
||||
{"atilde", "ã"}, // latin small letter a with tilde, U+00E3 ISOlat1
|
||||
{"auml", "ä"}, // latin small letter a with diaeresis, U+00E4 ISOlat1
|
||||
{"aring", "å"}, // latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1
|
||||
{"aelig", "æ"}, // latin small letter ae = latin small ligature ae, U+00E6 ISOlat1
|
||||
{"ccedil", "ç"}, // latin small letter c with cedilla, U+00E7 ISOlat1
|
||||
{"egrave", "è"}, // latin small letter e with grave, U+00E8 ISOlat1
|
||||
{"eacute", "é"}, // latin small letter e with acute, U+00E9 ISOlat1
|
||||
{"ecirc", "ê"}, // latin small letter e with circumflex, U+00EA ISOlat1
|
||||
{"euml", "ë"}, // latin small letter e with diaeresis, U+00EB ISOlat1
|
||||
{"igrave", "ì"}, // latin small letter i with grave, U+00EC ISOlat1
|
||||
{"iacute", "í"}, // latin small letter i with acute, U+00ED ISOlat1
|
||||
{"icirc", "î"}, // latin small letter i with circumflex, U+00EE ISOlat1
|
||||
{"iuml", "ï"}, // latin small letter i with diaeresis, U+00EF ISOlat1
|
||||
{"eth", "ð"}, // latin small letter eth, U+00F0 ISOlat1
|
||||
{"ntilde", "ñ"}, // latin small letter n with tilde, U+00F1 ISOlat1
|
||||
{"ograve", "ò"}, // latin small letter o with grave, U+00F2 ISOlat1
|
||||
{"oacute", "ó"}, // latin small letter o with acute, U+00F3 ISOlat1
|
||||
{"ocirc", "ô"}, // latin small letter o with circumflex, U+00F4 ISOlat1
|
||||
{"otilde", "õ"}, // latin small letter o with tilde, U+00F5 ISOlat1
|
||||
{"ouml", "ö"}, // latin small letter o with diaeresis, U+00F6 ISOlat1
|
||||
{"divide", "÷"}, // division sign, U+00F7 ISOnum
|
||||
{"oslash", "ø"}, // latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1
|
||||
{"ugrave", "ù"}, // latin small letter u with grave, U+00F9 ISOlat1
|
||||
{"uacute", "ú"}, // latin small letter u with acute, U+00FA ISOlat1
|
||||
{"ucirc", "û"}, // latin small letter u with circumflex, U+00FB ISOlat1
|
||||
{"uuml", "ü"}, // latin small letter u with diaeresis, U+00FC ISOlat1
|
||||
{"yacute", "ý"}, // latin small letter y with acute, U+00FD ISOlat1
|
||||
{"thorn", "þ"}, // latin small letter thorn, U+00FE ISOlat1
|
||||
{"yuml", "ÿ"}, // latin small letter y with diaeresis, U+00FF ISOlat1
|
||||
|
||||
// Latin Extended-A
|
||||
{"OElig", "Œ"}, // latin capital ligature OE, U+0152 ISOlat2
|
||||
{"oelig", "œ"}, // latin small ligature oe, U+0153 ISOlat2
|
||||
// ligature is a misnomer, this is a separate character in some languages
|
||||
{"Scaron", "Š"}, // latin capital letter S with caron, U+0160 ISOlat2
|
||||
{"scaron", "š"}, // latin small letter s with caron, U+0161 ISOlat2
|
||||
{"Yuml", "Ÿ"}, // latin capital letter Y with diaeresis, U+0178 ISOlat2
|
||||
|
||||
// Spacing Modifier Letters
|
||||
{"circ", "ˆ"}, // modifier letter circumflex accent, U+02C6 ISOpub
|
||||
{"tilde", "˜"}, // small tilde, U+02DC ISOdia
|
||||
|
||||
// General Punctuation
|
||||
{"ensp", " "}, // en space, U+2002 ISOpub
|
||||
{"emsp", " "}, // em space, U+2003 ISOpub
|
||||
{"thinsp", " "}, // thin space, U+2009 ISOpub
|
||||
{"zwnj", "‌"}, // zero width non-joiner, U+200C NEW RFC 2070
|
||||
{"zwj", "‍"}, // zero width joiner, U+200D NEW RFC 2070
|
||||
{"lrm", "‎"}, // left-to-right mark, U+200E NEW RFC 2070
|
||||
{"rlm", "‏"}, // right-to-left mark, U+200F NEW RFC 2070
|
||||
{"ndash", "–"}, // en dash, U+2013 ISOpub
|
||||
{"mdash", "—"}, // em dash, U+2014 ISOpub
|
||||
{"lsquo", "‘"}, // left single quotation mark, U+2018 ISOnum
|
||||
{"rsquo", "’"}, // right single quotation mark, U+2019 ISOnum
|
||||
{"sbquo", "‚"}, // single low-9 quotation mark, U+201A NEW
|
||||
{"ldquo", "“"}, // left double quotation mark, U+201C ISOnum
|
||||
{"rdquo", "”"}, // right double quotation mark, U+201D ISOnum
|
||||
{"bdquo", "„"}, // double low-9 quotation mark, U+201E NEW
|
||||
{"dagger", "†"}, // dagger, U+2020 ISOpub
|
||||
{"Dagger", "‡"}, // double dagger, U+2021 ISOpub
|
||||
{"permil", "‰"}, // per mille sign, U+2030 ISOtech
|
||||
{"lsaquo", "‹"}, // single left-pointing angle quotation mark, U+2039 ISO proposed
|
||||
// lsaquo is proposed but not yet ISO standardized
|
||||
{"rsaquo", "›"}, // single right-pointing angle quotation mark, U+203A ISO proposed
|
||||
// rsaquo is proposed but not yet ISO standardized
|
||||
|
||||
// Currency Symbols
|
||||
{"euro", "€"}, // euro sign, U+20AC NEW
|
||||
|
||||
// Latin Extended-B
|
||||
{"fnof", "ƒ"}, // latin small letter f with hook = function = florin, U+0192 ISOtech
|
||||
|
||||
// Greek
|
||||
{"Alpha", "Α"}, // greek capital letter alpha, U+0391
|
||||
{"Beta", "Β"}, // greek capital letter beta, U+0392
|
||||
{"Gamma", "Γ"}, // greek capital letter gamma, U+0393 ISOgrk3
|
||||
{"Delta", "Δ"}, // greek capital letter delta, U+0394 ISOgrk3
|
||||
{"Epsilon", "Ε"}, // greek capital letter epsilon, U+0395
|
||||
{"Zeta", "Ζ"}, // greek capital letter zeta, U+0396
|
||||
{"Eta", "Η"}, // greek capital letter eta, U+0397
|
||||
{"Theta", "Θ"}, // greek capital letter theta, U+0398 ISOgrk3
|
||||
{"Iota", "Ι"}, // greek capital letter iota, U+0399
|
||||
{"Kappa", "Κ"}, // greek capital letter kappa, U+039A
|
||||
{"Lambda", "Λ"}, // greek capital letter lamda, U+039B ISOgrk3
|
||||
{"Mu", "Μ"}, // greek capital letter mu, U+039C
|
||||
{"Nu", "Ν"}, // greek capital letter nu, U+039D
|
||||
{"Xi", "Ξ"}, // greek capital letter xi, U+039E ISOgrk3
|
||||
{"Omicron", "Ο"}, // greek capital letter omicron, U+039F
|
||||
{"Pi", "Π"}, // greek capital letter pi, U+03A0 ISOgrk3
|
||||
{"Rho", "Ρ"}, // greek capital letter rho, U+03A1
|
||||
{"Sigma", "Σ"}, // greek capital letter sigma, U+03A3 ISOgrk3
|
||||
{"Tau", "Τ"}, // greek capital letter tau, U+03A4
|
||||
{"Upsilon", "Υ"}, // greek capital letter upsilon, U+03A5 ISOgrk3
|
||||
{"Phi", "Φ"}, // greek capital letter phi, U+03A6 ISOgrk3
|
||||
{"Chi", "Χ"}, // greek capital letter chi, U+03A7
|
||||
{"Psi", "Ψ"}, // greek capital letter psi, U+03A8 ISOgrk3
|
||||
{"Omega", "Ω"}, // greek capital letter omega, U+03A9 ISOgrk3
|
||||
{"alpha", "α"}, // greek small letter alpha, U+03B1 ISOgrk3
|
||||
{"beta", "β"}, // greek small letter beta, U+03B2 ISOgrk3
|
||||
{"gamma", "γ"}, // greek small letter gamma, U+03B3 ISOgrk3
|
||||
{"delta", "δ"}, // greek small letter delta, U+03B4 ISOgrk3
|
||||
{"epsilon", "ε"}, // greek small letter epsilon, U+03B5 ISOgrk3
|
||||
{"zeta", "ζ"}, // greek small letter zeta, U+03B6 ISOgrk3
|
||||
{"eta", "η"}, // greek small letter eta, U+03B7 ISOgrk3
|
||||
{"theta", "θ"}, // greek small letter theta, U+03B8 ISOgrk3
|
||||
{"iota", "ι"}, // greek small letter iota, U+03B9 ISOgrk3
|
||||
{"kappa", "κ"}, // greek small letter kappa, U+03BA ISOgrk3
|
||||
{"lambda", "λ"}, // greek small letter lamda, U+03BB ISOgrk3
|
||||
{"mu", "μ"}, // greek small letter mu, U+03BC ISOgrk3
|
||||
{"nu", "ν"}, // greek small letter nu, U+03BD ISOgrk3
|
||||
{"xi", "ξ"}, // greek small letter xi, U+03BE ISOgrk3
|
||||
{"omicron", "ο"}, // greek small letter omicron, U+03BF NEW
|
||||
{"pi", "π"}, // greek small letter pi, U+03C0 ISOgrk3
|
||||
{"rho", "ρ"}, // greek small letter rho, U+03C1 ISOgrk3
|
||||
{"sigmaf", "ς"}, // greek small letter final sigma, U+03C2 ISOgrk3
|
||||
{"sigma", "σ"}, // greek small letter sigma, U+03C3 ISOgrk3
|
||||
{"tau", "τ"}, // greek small letter tau, U+03C4 ISOgrk3
|
||||
{"upsilon", "υ"}, // greek small letter upsilon, U+03C5 ISOgrk3
|
||||
{"phi", "φ"}, // greek small letter phi, U+03C6 ISOgrk3
|
||||
{"chi", "χ"}, // greek small letter chi, U+03C7 ISOgrk3
|
||||
{"psi", "ψ"}, // greek small letter psi, U+03C8 ISOgrk3
|
||||
{"omega", "ω"}, // greek small letter omega, U+03C9 ISOgrk3
|
||||
{"thetasym", "ϑ"}, // greek theta symbol, U+03D1 NEW
|
||||
{"upsih", "ϒ"}, // greek upsilon with hook symbol, U+03D2 NEW
|
||||
{"piv", "ϖ"}, // greek pi symbol, U+03D6 ISOgrk3
|
||||
|
||||
// General Punctuation
|
||||
{"bull", "•"}, // bullet = black small circle, U+2022 ISOpub
|
||||
// bullet is NOT the same as bullet operator, U+2219
|
||||
{"hellip", "…"}, // horizontal ellipsis = three dot leader, U+2026 ISOpub
|
||||
{"prime", "′"}, // prime = minutes = feet, U+2032 ISOtech
|
||||
{"Prime", "″"}, // double prime = seconds = inches, U+2033 ISOtech
|
||||
{"oline", "‾"}, // overline = spacing overscore, U+203E NEW
|
||||
{"frasl", "⁄"}, // fraction slash, U+2044 NEW
|
||||
|
||||
// Letterlike Symbols
|
||||
{"weierp", "℘"}, // script capital P = power set = Weierstrass p, U+2118 ISOamso
|
||||
{"image", "ℑ"}, // black-letter capital I = imaginary part, U+2111 ISOamso
|
||||
{"real", "ℜ"}, // black-letter capital R = real part symbol, U+211C ISOamso
|
||||
{"trade", "™"}, // trade mark sign, U+2122 ISOnum
|
||||
{"alefsym", "ℵ"}, // alef symbol = first transfinite cardinal, U+2135 NEW
|
||||
// alef symbol is NOT the same as hebrew letter alef,
|
||||
// U+05D0 although the same glyph could be used to depict both characters
|
||||
|
||||
// Arrows
|
||||
{"larr", "←"}, // leftwards arrow, U+2190 ISOnum
|
||||
{"uarr", "↑"}, // upwards arrow, U+2191 ISOnum
|
||||
{"rarr", "→"}, // rightwards arrow, U+2192 ISOnum
|
||||
{"darr", "↓"}, // downwards arrow, U+2193 ISOnum
|
||||
{"harr", "↔"}, // left right arrow, U+2194 ISOamsa
|
||||
{"crarr", "↵"}, // downwards arrow with corner leftwards = carriage return, U+21B5 NEW
|
||||
{"lArr", "⇐"}, // leftwards double arrow, U+21D0 ISOtech
|
||||
// Unicode does not say that lArr is the same as the 'is implied by' arrow
|
||||
// but also does not have any other character for that function. So lArr can
|
||||
// be used for 'is implied by' as ISOtech suggests
|
||||
{"uArr", "⇑"}, // upwards double arrow, U+21D1 ISOamsa
|
||||
{"rArr", "⇒"}, // rightwards double arrow, U+21D2 ISOtech
|
||||
// Unicode does not say this is the 'implies' character but does not have
|
||||
// another character with this function so rArr can be used for 'implies'
|
||||
// as ISOtech suggests
|
||||
{"dArr", "⇓"}, // downwards double arrow, U+21D3 ISOamsa
|
||||
{"hArr", "⇔"}, // left right double arrow, U+21D4 ISOamsa
|
||||
|
||||
// Mathematical Operators
|
||||
{"forall", "∀"}, // for all, U+2200 ISOtech
|
||||
{"part", "∂"}, // partial differential, U+2202 ISOtech
|
||||
{"exist", "∃"}, // there exists, U+2203 ISOtech
|
||||
{"empty", "∅"}, // empty set = null set, U+2205 ISOamso
|
||||
{"nabla", "∇"}, // nabla = backward difference, U+2207 ISOtech
|
||||
{"isin", "∈"}, // element of, U+2208 ISOtech
|
||||
{"notin", "∉"}, // not an element of, U+2209 ISOtech
|
||||
{"ni", "∋"}, // contains as member, U+220B ISOtech
|
||||
{"prod", "∏"}, // n-ary product = product sign, U+220F ISOamsb
|
||||
// prod is NOT the same character as U+03A0 'greek capital letter pi' though
|
||||
// the same glyph might be used for both
|
||||
{"sum", "∑"}, // n-ary summation, U+2211 ISOamsb
|
||||
// sum is NOT the same character as U+03A3 'greek capital letter sigma'
|
||||
// though the same glyph might be used for both
|
||||
{"minus", "−"}, // minus sign, U+2212 ISOtech
|
||||
{"lowast", "∗"}, // asterisk operator, U+2217 ISOtech
|
||||
{"radic", "√"}, // square root = radical sign, U+221A ISOtech
|
||||
{"prop", "∝"}, // proportional to, U+221D ISOtech
|
||||
{"infin", "∞"}, // infinity, U+221E ISOtech
|
||||
{"ang", "∠"}, // angle, U+2220 ISOamso
|
||||
{"and", "∧"}, // logical and = wedge, U+2227 ISOtech
|
||||
{"or", "∨"}, // logical or = vee, U+2228 ISOtech
|
||||
{"cap", "∩"}, // intersection = cap, U+2229 ISOtech
|
||||
{"cup", "∪"}, // union = cup, U+222A ISOtech
|
||||
{"int", "∫"}, // integral, U+222B ISOtech
|
||||
{"there4", "∴"}, // therefore, U+2234 ISOtech
|
||||
{"sim", "∼"}, // tilde operator = varies with = similar to, U+223C ISOtech
|
||||
// tilde operator is NOT the same character as the tilde, U+007E,
|
||||
// although the same glyph might be used to represent both
|
||||
{"cong", "≅"}, // approximately equal to, U+2245 ISOtech
|
||||
{"asymp", "≈"}, // almost equal to = asymptotic to, U+2248 ISOamsr
|
||||
{"ne", "≠"}, // not equal to, U+2260 ISOtech
|
||||
{"equiv", "≡"}, // identical to, U+2261 ISOtech
|
||||
{"le", "≤"}, // less-than or equal to, U+2264 ISOtech
|
||||
{"ge", "≥"}, // greater-than or equal to, U+2265 ISOtech
|
||||
{"sub", "⊂"}, // subset of, U+2282 ISOtech
|
||||
{"sup", "⊃"}, // superset of, U+2283 ISOtech
|
||||
{"nsub", "⊄"}, // not a subset of, U+2284 ISOamsn
|
||||
{"sube", "⊆"}, // subset of or equal to, U+2286 ISOtech
|
||||
{"supe", "⊇"}, // superset of or equal to, U+2287 ISOtech
|
||||
{"oplus", "⊕"}, // circled plus = direct sum, U+2295 ISOamsb
|
||||
{"otimes", "⊗"}, // circled times = vector product, U+2297 ISOamsb
|
||||
{"perp", "⊥"}, // up tack = orthogonal to = perpendicular, U+22A5 ISOtech
|
||||
{"sdot", "⋅"}, // dot operator, U+22C5 ISOamsb
|
||||
// dot operator is NOT the same character as U+00B7 middle dot
|
||||
|
||||
// Miscellaneous Technical
|
||||
{"lceil", "⌈"}, // left ceiling = APL upstile, U+2308 ISOamsc
|
||||
{"rceil", "⌉"}, // right ceiling, U+2309 ISOamsc
|
||||
{"lfloor", "⌊"}, // left floor = APL downstile, U+230A ISOamsc
|
||||
{"rfloor", "⌋"}, // right floor, U+230B ISOamsc
|
||||
{"lang", "〈"}, // left-pointing angle bracket = bra, U+2329 ISOtech
|
||||
// lang is NOT the same character as U+003C 'less than sign'
|
||||
// or U+2039 'single left-pointing angle quotation mark'
|
||||
{"rang", "〉"}, // right-pointing angle bracket = ket, U+232A ISOtech
|
||||
// rang is NOT the same character as U+003E 'greater than sign'
|
||||
// or U+203A 'single right-pointing angle quotation mark'
|
||||
|
||||
// Geometric Shapes
|
||||
{"loz", "◊"}, // lozenge, U+25CA ISOpub
|
||||
|
||||
// Miscellaneous Symbols
|
||||
{"spades", "♠"}, // black spade suit, U+2660 ISOpub
|
||||
{"clubs", "♣"}, // black club suit = shamrock, U+2663 ISOpub
|
||||
{"hearts", "♥"}, // black heart suit = valentine, U+2665 ISOpub
|
||||
{"diams", "♦"} // black diamond suit, U+2666 ISOpub
|
||||
}))
|
||||
|
||||
class XmlStreamEntityResolver : public QXmlStreamEntityResolver
|
||||
{
|
||||
public:
|
||||
QString resolveUndeclaredEntity(const QString &name) override
|
||||
{
|
||||
return HTML_ENTITIES->value(name);
|
||||
}
|
||||
};
|
||||
|
||||
const char shortDay[][4] = {
|
||||
"Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat",
|
||||
@@ -543,8 +228,6 @@ void Parser::parse(const QByteArray &feedData)
|
||||
void Parser::parse_impl(const QByteArray &feedData)
|
||||
{
|
||||
QXmlStreamReader xml(feedData);
|
||||
XmlStreamEntityResolver resolver;
|
||||
xml.setEntityResolver(&resolver);
|
||||
bool foundChannel = false;
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -48,7 +50,7 @@ namespace RSS
|
||||
QList<QVariantHash> articles;
|
||||
};
|
||||
|
||||
class Parser : public QObject
|
||||
class Parser: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -38,19 +38,6 @@
|
||||
|
||||
using namespace RSS;
|
||||
|
||||
namespace
|
||||
{
|
||||
QVariantHash articleDataFromJSON(const QJsonObject &jsonObj)
|
||||
{
|
||||
auto varHash = jsonObj.toVariantHash();
|
||||
// JSON object store DateTime as string so we need to convert it
|
||||
varHash[Article::KeyDate] =
|
||||
QDateTime::fromString(jsonObj.value(Article::KeyDate).toString(), Qt::RFC2822Date);
|
||||
|
||||
return varHash;
|
||||
}
|
||||
}
|
||||
|
||||
const QString Article::KeyId(QStringLiteral("id"));
|
||||
const QString Article::KeyDate(QStringLiteral("date"));
|
||||
const QString Article::KeyTitle(QStringLiteral("title"));
|
||||
@@ -73,9 +60,6 @@ Article::Article(Feed *feed, const QVariantHash &varHash)
|
||||
, m_isRead(varHash.value(KeyIsRead, false).toBool())
|
||||
, m_data(varHash)
|
||||
{
|
||||
if (!m_date.isValid())
|
||||
throw std::runtime_error("Bad RSS Article data");
|
||||
|
||||
// If item does not have a guid, fall back to some other identifier
|
||||
if (m_guid.isEmpty())
|
||||
m_guid = varHash.value(KeyTorrentURL).toString();
|
||||
@@ -93,8 +77,11 @@ Article::Article(Feed *feed, const QVariantHash &varHash)
|
||||
}
|
||||
|
||||
Article::Article(Feed *feed, const QJsonObject &jsonObj)
|
||||
: Article(feed, articleDataFromJSON(jsonObj))
|
||||
: Article(feed, jsonObj.toVariantHash())
|
||||
{
|
||||
// JSON object store DateTime as string so we need to convert it
|
||||
m_date = QDateTime::fromString(jsonObj.value(KeyDate).toString(), Qt::RFC2822Date);
|
||||
m_data[KeyDate] = m_date;
|
||||
}
|
||||
|
||||
QString Article::guid() const
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace RSS
|
||||
{
|
||||
class Feed;
|
||||
|
||||
class Article : public QObject
|
||||
class Article: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Article)
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include "../bittorrent/magneturi.h"
|
||||
#include "../bittorrent/session.h"
|
||||
#include "../asyncfilestorage.h"
|
||||
#include "../global.h"
|
||||
#include "../logger.h"
|
||||
#include "../profile.h"
|
||||
#include "../settingsstorage.h"
|
||||
@@ -65,7 +64,6 @@ const QString ConfFolderName(QStringLiteral("rss"));
|
||||
const QString RulesFileName(QStringLiteral("download_rules.json"));
|
||||
|
||||
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
||||
const QString SettingsKey_SmartEpisodeFilter(QStringLiteral("RSS/AutoDownloader/SmartEpisodeFilter"));
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -97,11 +95,6 @@ using namespace RSS;
|
||||
|
||||
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
||||
|
||||
QString computeSmartFilterRegex(const QStringList &filters)
|
||||
{
|
||||
return QString("(?:_|\\b)(?:%1)(?:_|\\b)").arg(filters.join(QString(")|(?:")));
|
||||
}
|
||||
|
||||
AutoDownloader::AutoDownloader()
|
||||
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false).toBool())
|
||||
, m_processingTimer(new QTimer(this))
|
||||
@@ -120,7 +113,7 @@ AutoDownloader::AutoDownloader()
|
||||
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
||||
{
|
||||
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
||||
.arg(fileName, errorString), Log::CRITICAL);
|
||||
.arg(fileName).arg(errorString), Log::CRITICAL);
|
||||
});
|
||||
|
||||
m_ioThread->start();
|
||||
@@ -130,13 +123,6 @@ AutoDownloader::AutoDownloader()
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed
|
||||
, this, &AutoDownloader::handleTorrentDownloadFailed);
|
||||
|
||||
// initialise the smart episode regex
|
||||
const QString regex = computeSmartFilterRegex(smartEpisodeFilters());
|
||||
m_smartEpisodeRegex = QRegularExpression(regex,
|
||||
QRegularExpression::CaseInsensitiveOption
|
||||
| QRegularExpression::ExtendedPatternSyntaxOption
|
||||
| QRegularExpression::UseUnicodePropertiesOption);
|
||||
|
||||
load();
|
||||
|
||||
m_processingTimer->setSingleShot(true);
|
||||
@@ -240,7 +226,7 @@ void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFi
|
||||
QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
for (const auto &rule : copyAsConst(rules()))
|
||||
for (const auto &rule : rules())
|
||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||
|
||||
return QJsonDocument(jsonObj).toJson();
|
||||
@@ -248,14 +234,15 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||
|
||||
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
|
||||
{
|
||||
for (const auto &rule : copyAsConst(rulesFromJSON(data)))
|
||||
const auto rules = rulesFromJSON(data);
|
||||
for (const auto &rule : rules)
|
||||
insertRule(rule);
|
||||
}
|
||||
|
||||
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
||||
{
|
||||
QVariantHash dict;
|
||||
for (const auto &rule : copyAsConst(rules()))
|
||||
for (const auto &rule : rules())
|
||||
dict[rule.name()] = rule.toLegacyDict();
|
||||
|
||||
QByteArray data;
|
||||
@@ -275,41 +262,10 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
|
||||
if (in.status() != QDataStream::Ok)
|
||||
throw ParsingError(tr("Invalid data format"));
|
||||
|
||||
for (const QVariant &val : qAsConst(dict))
|
||||
for (const QVariant &val : dict)
|
||||
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
|
||||
}
|
||||
|
||||
QStringList AutoDownloader::smartEpisodeFilters() const
|
||||
{
|
||||
const QVariant filtersSetting = SettingsStorage::instance()->loadValue(SettingsKey_SmartEpisodeFilter);
|
||||
|
||||
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
|
||||
"(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})" // Format 4: 01.01.2017
|
||||
};
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
return filtersSetting.toStringList();
|
||||
}
|
||||
|
||||
QRegularExpression AutoDownloader::smartEpisodeRegex() const
|
||||
{
|
||||
return m_smartEpisodeRegex;
|
||||
}
|
||||
|
||||
void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
|
||||
{
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_SmartEpisodeFilter, filters);
|
||||
|
||||
const QString regex = computeSmartFilterRegex(filters);
|
||||
m_smartEpisodeRegex.setPattern(regex);
|
||||
}
|
||||
|
||||
void AutoDownloader::process()
|
||||
{
|
||||
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
||||
@@ -365,8 +321,18 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||
for (AutoDownloadRule &rule: m_rules) {
|
||||
if (!rule.isEnabled()) continue;
|
||||
if (!rule.feedURLs().contains(job->feedURL)) continue;
|
||||
if (!rule.accepts(job->articleData)) continue;
|
||||
if (!rule.matches(job->articleData.value(Article::KeyTitle).toString())) continue;
|
||||
|
||||
auto articleDate = job->articleData.value(Article::KeyDate).toDateTime();
|
||||
// if rule is in ignoring state do nothing with matched torrent
|
||||
if (rule.ignoreDays() > 0) {
|
||||
if (rule.lastMatch().isValid()) {
|
||||
if (articleDate < rule.lastMatch().addDays(rule.ignoreDays()))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rule.setLastMatch(articleDate);
|
||||
m_dirty = true;
|
||||
storeDeferred();
|
||||
|
||||
@@ -374,8 +340,6 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||
params.savePath = rule.savePath();
|
||||
params.category = rule.assignedCategory();
|
||||
params.addPaused = rule.addPaused();
|
||||
if (!rule.savePath().isEmpty())
|
||||
params.useAutoTMM = TriStateBool::False;
|
||||
auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
|
||||
BitTorrent::Session::instance()->addTorrent(torrentURL, params);
|
||||
|
||||
@@ -405,7 +369,7 @@ void AutoDownloader::load()
|
||||
loadRules(rulesFile.readAll());
|
||||
else
|
||||
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
||||
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
|
||||
.arg(rulesFile.fileName()).arg(rulesFile.errorString()), Log::CRITICAL);
|
||||
}
|
||||
|
||||
void AutoDownloader::loadRules(const QByteArray &data)
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QRegularExpression>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class QThread;
|
||||
@@ -59,7 +58,7 @@ namespace RSS
|
||||
QString message() const;
|
||||
};
|
||||
|
||||
class AutoDownloader final : public QObject
|
||||
class AutoDownloader final: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AutoDownloader)
|
||||
@@ -81,10 +80,6 @@ namespace RSS
|
||||
bool isProcessingEnabled() const;
|
||||
void setProcessingEnabled(bool enabled);
|
||||
|
||||
QStringList smartEpisodeFilters() const;
|
||||
void setSmartEpisodeFilters(const QStringList &filters);
|
||||
QRegularExpression smartEpisodeRegex() const;
|
||||
|
||||
bool hasRule(const QString &ruleName) const;
|
||||
AutoDownloadRule ruleByName(const QString &ruleName) const;
|
||||
QList<AutoDownloadRule> rules() const;
|
||||
@@ -137,6 +132,5 @@ namespace RSS
|
||||
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
|
||||
bool m_dirty = false;
|
||||
QBasicTimer m_savingTimer;
|
||||
QRegularExpression m_smartEpisodeRegex;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,19 +34,18 @@
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QSharedData>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "../global.h"
|
||||
#include "../preferences.h"
|
||||
#include "../tristatebool.h"
|
||||
#include "../utils/fs.h"
|
||||
#include "../utils/string.h"
|
||||
#include "rss_article.h"
|
||||
#include "rss_autodownloader.h"
|
||||
#include "rss_feed.h"
|
||||
#include "rss_article.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -101,12 +100,10 @@ const QString Str_AssignedCategory(QStringLiteral("assignedCategory"));
|
||||
const QString Str_LastMatch(QStringLiteral("lastMatch"));
|
||||
const QString Str_IgnoreDays(QStringLiteral("ignoreDays"));
|
||||
const QString Str_AddPaused(QStringLiteral("addPaused"));
|
||||
const QString Str_SmartFilter(QStringLiteral("smartFilter"));
|
||||
const QString Str_PreviouslyMatched(QStringLiteral("previouslyMatchedEpisodes"));
|
||||
|
||||
namespace RSS
|
||||
{
|
||||
struct AutoDownloadRuleData : public QSharedData
|
||||
struct AutoDownloadRuleData: public QSharedData
|
||||
{
|
||||
QString name;
|
||||
bool enabled = true;
|
||||
@@ -123,10 +120,6 @@ namespace RSS
|
||||
QString category;
|
||||
TriStateBool addPaused = TriStateBool::Undefined;
|
||||
|
||||
bool smartFilter = false;
|
||||
QStringList previouslyMatchedEpisodes;
|
||||
|
||||
mutable QString lastComputedEpisode;
|
||||
mutable QHash<QString, QRegularExpression> cachedRegexes;
|
||||
|
||||
bool operator==(const AutoDownloadRuleData &other) const
|
||||
@@ -142,38 +135,13 @@ namespace RSS
|
||||
&& (lastMatch == other.lastMatch)
|
||||
&& (savePath == other.savePath)
|
||||
&& (category == other.category)
|
||||
&& (addPaused == other.addPaused)
|
||||
&& (smartFilter == other.smartFilter);
|
||||
&& (addPaused == other.addPaused);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
using namespace RSS;
|
||||
|
||||
QString computeEpisodeName(const QString &article)
|
||||
{
|
||||
const QRegularExpression episodeRegex = AutoDownloader::instance()->smartEpisodeRegex();
|
||||
const QRegularExpressionMatch match = episodeRegex.match(article);
|
||||
|
||||
// See if we can extract an season/episode number or date from the title
|
||||
if (!match.hasMatch())
|
||||
return QString();
|
||||
|
||||
QStringList ret;
|
||||
for (int i = 1; i <= match.lastCapturedIndex(); ++i) {
|
||||
QString cap = match.captured(i);
|
||||
|
||||
if (cap.isEmpty())
|
||||
continue;
|
||||
|
||||
bool isInt = false;
|
||||
int x = cap.toInt(&isInt);
|
||||
|
||||
ret.append(isInt ? QString::number(x) : cap);
|
||||
}
|
||||
return ret.join('x');
|
||||
}
|
||||
|
||||
AutoDownloadRule::AutoDownloadRule(const QString &name)
|
||||
: m_dataPtr(new AutoDownloadRuleData)
|
||||
{
|
||||
@@ -193,198 +161,180 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, bool
|
||||
// The cache is cleared whenever the regex/wildcard, must or must not contain fields or
|
||||
// episode filter are modified.
|
||||
Q_ASSERT(!expression.isEmpty());
|
||||
QRegularExpression regex(m_dataPtr->cachedRegexes[expression]);
|
||||
|
||||
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
||||
if (regex.pattern().isEmpty()) {
|
||||
regex = QRegularExpression {
|
||||
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
|
||||
, QRegularExpression::CaseInsensitiveOption};
|
||||
}
|
||||
if (!regex.pattern().isEmpty())
|
||||
return regex;
|
||||
|
||||
return regex;
|
||||
return m_dataPtr->cachedRegexes[expression] = QRegularExpression(isRegex ? expression : Utils::String::wildcardToRegex(expression), QRegularExpression::CaseInsensitiveOption);
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesExpression(const QString &articleTitle, const QString &expression) const
|
||||
bool AutoDownloadRule::matches(const QString &articleTitle, const QString &expression) const
|
||||
{
|
||||
const QRegularExpression whitespace {"\\s+"};
|
||||
static QRegularExpression whitespace("\\s+");
|
||||
|
||||
if (expression.isEmpty()) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_dataPtr->useRegex) {
|
||||
else if (m_dataPtr->useRegex) {
|
||||
QRegularExpression reg(cachedRegex(expression));
|
||||
return reg.match(articleTitle).hasMatch();
|
||||
}
|
||||
else {
|
||||
// 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 *).
|
||||
foreach (const QString &wildcard, expression.split(whitespace, QString::SplitBehavior::SkipEmptyParts)) {
|
||||
QRegularExpression reg(cachedRegex(wildcard, false));
|
||||
|
||||
// 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) {
|
||||
const QRegularExpression reg {cachedRegex(wildcard, false)};
|
||||
if (!reg.match(articleTitle).hasMatch())
|
||||
return false;
|
||||
if (!reg.match(articleTitle).hasMatch())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle) const
|
||||
bool AutoDownloadRule::matches(const QString &articleTitle) const
|
||||
{
|
||||
if (m_dataPtr->mustContain.empty())
|
||||
return true;
|
||||
if (!m_dataPtr->mustContain.empty()) {
|
||||
bool logged = false;
|
||||
bool foundMustContain = false;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Accept if any complete expression matches.
|
||||
for (const QString &expression : qAsConst(m_dataPtr->mustContain)) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matchesExpression(articleTitle, expression))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesMustNotContainExpression(const QString& articleTitle) const
|
||||
{
|
||||
if (m_dataPtr->mustNotContain.empty())
|
||||
return true;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Reject if any complete expression matches.
|
||||
for (const QString &expression : qAsConst(m_dataPtr->mustNotContain)) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matchesExpression(articleTitle, expression))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString& articleTitle) const
|
||||
{
|
||||
// Reset the lastComputedEpisode, we don't want to leak it between matches
|
||||
m_dataPtr->lastComputedEpisode.clear();
|
||||
|
||||
if (m_dataPtr->episodeFilter.isEmpty())
|
||||
return true;
|
||||
|
||||
const QRegularExpression filterRegex {cachedRegex("(^\\d{1,4})x(.*;$)")};
|
||||
const QRegularExpressionMatch matcher {filterRegex.match(m_dataPtr->episodeFilter)};
|
||||
if (!matcher.hasMatch())
|
||||
return false;
|
||||
|
||||
const QString season {matcher.captured(1)};
|
||||
const QStringList episodes {matcher.captured(2).split(';')};
|
||||
const int seasonOurs {season.toInt()};
|
||||
|
||||
for (QString episode : episodes) {
|
||||
if (episode.isEmpty())
|
||||
continue;
|
||||
|
||||
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
|
||||
while ((episode.size() > 1) && episode.startsWith('0'))
|
||||
episode = episode.right(episode.size() - 1);
|
||||
|
||||
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)"};
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
QRegularExpressionMatch matcher = cachedRegex(partialPattern1).match(articleTitle);
|
||||
bool matched = matcher.hasMatch();
|
||||
|
||||
if (!matched) {
|
||||
matcher = cachedRegex(partialPattern2).match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Accept if any complete expression matches.
|
||||
foreach (const QString &expression, m_dataPtr->mustContain) {
|
||||
if (!logged) {
|
||||
// qDebug() << "Checking matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expressions:") << m_dataPtr->mustContain.join("|");
|
||||
logged = true;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
const int seasonTheirs {matcher.captured(1).toInt()};
|
||||
const int episodeTheirs {matcher.captured(2).toInt()};
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
foundMustContain = matches(articleTitle, expression);
|
||||
|
||||
if (episode.endsWith('-')) { // Infinite range
|
||||
const int episodeOurs {episode.leftRef(episode.size() - 1).toInt()};
|
||||
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
|
||||
return true;
|
||||
if (foundMustContain) {
|
||||
// qDebug() << "Found matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expression:") << expression;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundMustContain)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_dataPtr->mustNotContain.empty()) {
|
||||
bool logged = false;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Reject if any complete expression matches.
|
||||
foreach (const QString &expression, m_dataPtr->mustNotContain) {
|
||||
if (!logged) {
|
||||
// qDebug() << "Checking not matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expressions:") << m_dataPtr->mustNotContain.join("|");
|
||||
logged = true;
|
||||
}
|
||||
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matches(articleTitle, expression)) {
|
||||
// qDebug() << "Found not matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expression:") << expression;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_dataPtr->episodeFilter.isEmpty()) {
|
||||
// qDebug() << "Checking episode filter:" << m_dataPtr->episodeFilter;
|
||||
QRegularExpression f(cachedRegex("(^\\d{1,4})x(.*;$)"));
|
||||
QRegularExpressionMatch matcher = f.match(m_dataPtr->episodeFilter);
|
||||
bool matched = matcher.hasMatch();
|
||||
|
||||
if (!matched)
|
||||
return false;
|
||||
|
||||
QString s = matcher.captured(1);
|
||||
QStringList eps = matcher.captured(2).split(";");
|
||||
int sOurs = s.toInt();
|
||||
|
||||
foreach (QString ep, eps) {
|
||||
if (ep.isEmpty())
|
||||
continue;
|
||||
|
||||
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
|
||||
while (ep.size() > 1 && ep.startsWith("0"))
|
||||
ep = ep.right(ep.size() - 1);
|
||||
|
||||
if (ep.indexOf('-') != -1) { // Range detected
|
||||
QString partialPattern1 = "\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)";
|
||||
QString partialPattern2 = "\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)";
|
||||
QRegularExpression reg(cachedRegex(partialPattern1));
|
||||
|
||||
if (ep.endsWith('-')) { // Infinite range
|
||||
int epOurs = ep.left(ep.size() - 1).toInt();
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
matcher = reg.match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
|
||||
if (!matched) {
|
||||
reg = QRegularExpression(cachedRegex(partialPattern2));
|
||||
matcher = reg.match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
int sTheirs = matcher.captured(1).toInt();
|
||||
int epTheirs = matcher.captured(2).toInt();
|
||||
if (((sTheirs == sOurs) && (epTheirs >= epOurs)) || (sTheirs > sOurs)) {
|
||||
// qDebug() << "Matched episode:" << ep;
|
||||
// qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Normal range
|
||||
const QStringList range {episode.split('-')};
|
||||
QStringList range = ep.split('-');
|
||||
Q_ASSERT(range.size() == 2);
|
||||
if (range.first().toInt() > range.last().toInt())
|
||||
continue; // Ignore this subrule completely
|
||||
|
||||
const int episodeOursFirst {range.first().toInt()};
|
||||
const int episodeOursLast {range.last().toInt()};
|
||||
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
|
||||
return true;
|
||||
int epOursFirst = range.first().toInt();
|
||||
int epOursLast = range.last().toInt();
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
matcher = reg.match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
|
||||
if (!matched) {
|
||||
reg = QRegularExpression(cachedRegex(partialPattern2));
|
||||
matcher = reg.match(articleTitle);
|
||||
matched = matcher.hasMatch();
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
int sTheirs = matcher.captured(1).toInt();
|
||||
int epTheirs = matcher.captured(2).toInt();
|
||||
if ((sTheirs == sOurs) && ((epOursFirst <= epTheirs) && (epOursLast >= epTheirs))) {
|
||||
// qDebug() << "Matched episode:" << ep;
|
||||
// qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Single number
|
||||
QString expStr("\\b(?:s0?" + s + "[ -_\\.]?" + "e0?" + ep + "|" + s + "x" + "0?" + ep + ")(?:\\D|\\b)");
|
||||
QRegularExpression reg(cachedRegex(expStr));
|
||||
if (reg.match(articleTitle).hasMatch()) {
|
||||
// qDebug() << "Matched episode:" << ep;
|
||||
// qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Single number
|
||||
const QString expStr {QString("\\b(?:s0?%1[ -_\\.]?e0?%2|%1x0?%2)(?:\\D|\\b)").arg(season, episode)};
|
||||
if (cachedRegex(expStr).match(articleTitle).hasMatch())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString& articleTitle) const
|
||||
{
|
||||
if (!useSmartFilter())
|
||||
return true;
|
||||
|
||||
const QString episodeStr = computeEpisodeName(articleTitle);
|
||||
if (episodeStr.isEmpty())
|
||||
return true;
|
||||
|
||||
// See if this episode has been downloaded before
|
||||
const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
|
||||
const bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive) || articleTitle.contains("PROPER", Qt::CaseInsensitive);
|
||||
if (previouslyMatched && !isRepack)
|
||||
return false;
|
||||
|
||||
m_dataPtr->lastComputedEpisode = episodeStr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matches(const QVariantHash &articleData) const
|
||||
{
|
||||
const QDateTime articleDate {articleData[Article::KeyDate].toDateTime()};
|
||||
if (ignoreDays() > 0) {
|
||||
if (lastMatch().isValid() && (articleDate < lastMatch().addDays(ignoreDays())))
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString articleTitle {articleData[Article::KeyTitle].toString()};
|
||||
if (!matchesMustContainExpression(articleTitle))
|
||||
return false;
|
||||
if (!matchesMustNotContainExpression(articleTitle))
|
||||
return false;
|
||||
if (!matchesEpisodeFilterExpression(articleTitle))
|
||||
return false;
|
||||
if (!matchesSmartEpisodeFilter(articleTitle))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::accepts(const QVariantHash &articleData)
|
||||
{
|
||||
if (!matches(articleData))
|
||||
return false;
|
||||
|
||||
setLastMatch(articleData[Article::KeyDate].toDateTime());
|
||||
|
||||
if (!m_dataPtr->lastComputedEpisode.isEmpty()) {
|
||||
// TODO: probably need to add a marker for PROPER/REPACK to avoid duplicate downloads
|
||||
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisode);
|
||||
m_dataPtr->lastComputedEpisode.clear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -417,9 +367,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
|
||||
, {Str_AssignedCategory, assignedCategory()}
|
||||
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
|
||||
, {Str_IgnoreDays, ignoreDays()}
|
||||
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())}
|
||||
, {Str_SmartFilter, useSmartFilter()}
|
||||
, {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}};
|
||||
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())}};
|
||||
}
|
||||
|
||||
AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, const QString &name)
|
||||
@@ -436,7 +384,6 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
||||
rule.setAddPaused(jsonValueToTriStateBool(jsonObj.value(Str_AddPaused)));
|
||||
rule.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date));
|
||||
rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt());
|
||||
rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false));
|
||||
|
||||
const QJsonValue feedsVal = jsonObj.value(Str_AffectedFeeds);
|
||||
QStringList feedURLs;
|
||||
@@ -446,17 +393,6 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
||||
feedURLs << urlVal.toString();
|
||||
rule.setFeedURLs(feedURLs);
|
||||
|
||||
const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched);
|
||||
QStringList previouslyMatched;
|
||||
if (previouslyMatchedVal.isString()) {
|
||||
previouslyMatched << previouslyMatchedVal.toString();
|
||||
}
|
||||
else {
|
||||
foreach (const QJsonValue &val, previouslyMatchedVal.toArray())
|
||||
previouslyMatched << val.toString();
|
||||
}
|
||||
rule.setPreviouslyMatchedEpisodes(previouslyMatched);
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
@@ -613,16 +549,6 @@ QString AutoDownloadRule::mustNotContain() const
|
||||
return m_dataPtr->mustNotContain.join("|");
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::useSmartFilter() const
|
||||
{
|
||||
return m_dataPtr->smartFilter;
|
||||
}
|
||||
|
||||
void AutoDownloadRule::setUseSmartFilter(bool enabled)
|
||||
{
|
||||
m_dataPtr->smartFilter = enabled;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::useRegex() const
|
||||
{
|
||||
return m_dataPtr->useRegex;
|
||||
@@ -634,16 +560,6 @@ void AutoDownloadRule::setUseRegex(bool enabled)
|
||||
m_dataPtr->cachedRegexes.clear();
|
||||
}
|
||||
|
||||
QStringList AutoDownloadRule::previouslyMatchedEpisodes() const
|
||||
{
|
||||
return m_dataPtr->previouslyMatchedEpisodes;
|
||||
}
|
||||
|
||||
void AutoDownloadRule::setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes)
|
||||
{
|
||||
m_dataPtr->previouslyMatchedEpisodes = previouslyMatchedEpisodes;
|
||||
}
|
||||
|
||||
QString AutoDownloadRule::episodeFilter() const
|
||||
{
|
||||
return m_dataPtr->episodeFilter;
|
||||
|
||||
@@ -66,14 +66,9 @@ namespace RSS
|
||||
void setLastMatch(const QDateTime &lastMatch);
|
||||
bool useRegex() const;
|
||||
void setUseRegex(bool enabled);
|
||||
bool useSmartFilter() const;
|
||||
void setUseSmartFilter(bool enabled);
|
||||
QString episodeFilter() const;
|
||||
void setEpisodeFilter(const QString &e);
|
||||
|
||||
QStringList previouslyMatchedEpisodes() const;
|
||||
void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes);
|
||||
|
||||
QString savePath() const;
|
||||
void setSavePath(const QString &savePath);
|
||||
TriStateBool addPaused() const;
|
||||
@@ -81,8 +76,7 @@ namespace RSS
|
||||
QString assignedCategory() const;
|
||||
void setCategory(const QString &category);
|
||||
|
||||
bool matches(const QVariantHash &articleData) const;
|
||||
bool accepts(const QVariantHash &articleData);
|
||||
bool matches(const QString &articleTitle) const;
|
||||
|
||||
AutoDownloadRule &operator=(const AutoDownloadRule &other);
|
||||
bool operator==(const AutoDownloadRule &other) const;
|
||||
@@ -95,11 +89,7 @@ namespace RSS
|
||||
static AutoDownloadRule fromLegacyDict(const QVariantHash &dict);
|
||||
|
||||
private:
|
||||
bool matchesMustContainExpression(const QString &articleTitle) const;
|
||||
bool matchesMustNotContainExpression(const QString &articleTitle) const;
|
||||
bool matchesEpisodeFilterExpression(const QString &articleTitle) const;
|
||||
bool matchesSmartEpisodeFilter(const QString &articleTitle) const;
|
||||
bool matchesExpression(const QString &articleTitle, const QString &expression) const;
|
||||
bool matches(const QString &articleTitle, const QString &expression) const;
|
||||
QRegularExpression cachedRegex(const QString &expression, bool isRegex = true) const;
|
||||
|
||||
QSharedDataPointer<AutoDownloadRuleData> m_dataPtr;
|
||||
|
||||
@@ -189,7 +189,7 @@ void Feed::handleDownloadFailed(const QString &url, const QString &error)
|
||||
m_isLoading = false;
|
||||
m_hasError = true;
|
||||
|
||||
LogMsg(tr("Failed to download RSS feed at '%1'. Reason: %2").arg(url, error)
|
||||
LogMsg(tr("Failed to download RSS feed at '%1'. Reason: %2").arg(url).arg(error)
|
||||
, Log::WARNING);
|
||||
|
||||
emit stateChanged(this);
|
||||
@@ -197,13 +197,12 @@ void Feed::handleDownloadFailed(const QString &url, const QString &error)
|
||||
|
||||
void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
||||
{
|
||||
m_hasError = !result.error.isEmpty();
|
||||
|
||||
// For some reason, the RSS feed may contain malformed XML data and it may not be
|
||||
// successfully parsed by the XML parser. We are still trying to load as many articles
|
||||
// as possible until we encounter corrupted data. So we can have some articles here
|
||||
// even in case of parsing error.
|
||||
if (!m_hasError || !result.articles.isEmpty()) {
|
||||
if (!result.error.isEmpty()) {
|
||||
m_hasError = true;
|
||||
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url).arg(result.error)
|
||||
, Log::WARNING);
|
||||
}
|
||||
else {
|
||||
if (title() != result.title) {
|
||||
m_title = result.title;
|
||||
emit titleChanged(this);
|
||||
@@ -212,13 +211,7 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
||||
m_lastBuildDate = result.lastBuildDate;
|
||||
|
||||
int newArticlesCount = 0;
|
||||
const QDateTime now {QDateTime::currentDateTime()};
|
||||
for (QVariantHash varHash : result.articles) {
|
||||
// if article has no publication date we use feed update time as a fallback
|
||||
QVariant &articleDate = varHash[Article::KeyDate];
|
||||
if (!articleDate.toDateTime().isValid())
|
||||
articleDate = now;
|
||||
|
||||
foreach (const QVariantHash &varHash, result.articles) {
|
||||
try {
|
||||
auto article = new Article(this, varHash);
|
||||
if (addArticle(article))
|
||||
@@ -230,15 +223,11 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
||||
}
|
||||
|
||||
m_dirty = (newArticlesCount > 0);
|
||||
|
||||
store();
|
||||
|
||||
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
|
||||
.arg(m_url, QString::number(newArticlesCount)));
|
||||
}
|
||||
|
||||
if (m_hasError) {
|
||||
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
|
||||
, Log::WARNING);
|
||||
m_hasError = false;
|
||||
LogMsg(tr("RSS feed at '%1' successfully updated. Added %2 new articles.")
|
||||
.arg(m_url).arg(newArticlesCount));
|
||||
}
|
||||
|
||||
m_isLoading = false;
|
||||
@@ -260,7 +249,7 @@ void Feed::load()
|
||||
}
|
||||
else {
|
||||
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
|
||||
.arg(m_dataFileName, file.errorString())
|
||||
.arg(m_dataFileName).arg(file.errorString())
|
||||
, Log::WARNING);
|
||||
}
|
||||
}
|
||||
@@ -400,7 +389,7 @@ void Feed::downloadIcon()
|
||||
// Download the RSS Feed icon
|
||||
// XXX: This works for most sites but it is not perfect
|
||||
const QUrl url(m_url);
|
||||
auto iconUrl = QString("%1://%2/favicon.ico").arg(url.scheme(), url.host());
|
||||
auto iconUrl = QString("%1://%2/favicon.ico").arg(url.scheme()).arg(url.host());
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl, true);
|
||||
connect(handler
|
||||
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace RSS
|
||||
struct ParsingResult;
|
||||
}
|
||||
|
||||
class Feed final : public Item
|
||||
class Feed final: public Item
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Feed)
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "rss_article.h"
|
||||
|
||||
using namespace RSS;
|
||||
@@ -123,7 +122,7 @@ void Folder::addItem(Item *item)
|
||||
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
|
||||
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
|
||||
|
||||
for (auto article: copyAsConst(item->articles()))
|
||||
for (auto article: item->articles())
|
||||
emit newArticle(article);
|
||||
|
||||
if (item->unreadCount() > 0)
|
||||
@@ -134,7 +133,7 @@ void Folder::removeItem(Item *item)
|
||||
{
|
||||
Q_ASSERT(m_items.contains(item));
|
||||
|
||||
for (auto article: copyAsConst(item->articles()))
|
||||
for (auto article: item->articles())
|
||||
emit articleAboutToBeRemoved(article);
|
||||
|
||||
item->disconnect(this);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace RSS
|
||||
{
|
||||
class Session;
|
||||
|
||||
class Folder final : public Item
|
||||
class Folder final: public Item
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Folder)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
using namespace RSS;
|
||||
|
||||
const QChar Item::PathSeparator('\\');
|
||||
const QString Item::PathSeparator("\\");
|
||||
|
||||
Item::Item(const QString &path)
|
||||
: m_path(path)
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace RSS
|
||||
class Folder;
|
||||
class Session;
|
||||
|
||||
class Item : public QObject
|
||||
class Item: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Item)
|
||||
@@ -58,7 +58,7 @@ namespace RSS
|
||||
|
||||
virtual QJsonValue toJsonValue(bool withData = false) const = 0;
|
||||
|
||||
static const QChar PathSeparator;
|
||||
static const QString PathSeparator;
|
||||
|
||||
static bool isValidPath(const QString &path);
|
||||
static QString joinPath(const QString &path1, const QString &path2);
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
#include "../utils/fs.h"
|
||||
#include "rss_article.h"
|
||||
#include "rss_feed.h"
|
||||
#include "rss_folder.h"
|
||||
#include "rss_item.h"
|
||||
#include "rss_folder.h"
|
||||
|
||||
const int MsecsPerMin = 60000;
|
||||
const QString ConfFolderName(QStringLiteral("rss"));
|
||||
@@ -79,7 +79,7 @@ Session::Session()
|
||||
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
||||
{
|
||||
Logger::instance()->addMessage(QString("Couldn't save RSS Session configuration in %1. Error: %2")
|
||||
.arg(fileName, errorString), Log::WARNING);
|
||||
.arg(fileName).arg(errorString), Log::WARNING);
|
||||
});
|
||||
|
||||
m_dataFileStorage = new AsyncFileStorage(
|
||||
@@ -89,7 +89,7 @@ Session::Session()
|
||||
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
||||
{
|
||||
Logger::instance()->addMessage(QString("Couldn't save RSS Session data in %1. Error: %2")
|
||||
.arg(fileName, errorString), Log::WARNING);
|
||||
.arg(fileName).arg(errorString), Log::WARNING);
|
||||
});
|
||||
|
||||
m_itemsByPath.insert("", new Folder); // root folder
|
||||
@@ -257,7 +257,7 @@ void Session::load()
|
||||
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);
|
||||
.arg(itemsFile.fileName()).arg(itemsFile.errorString()), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ void Session::load()
|
||||
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);
|
||||
.arg(itemsFile.fileName()).arg(jsonError.errorString()), Log::WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
else if (!val.isObject()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS Item '%1'. Invalid data format.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
.arg(QString("%1\\%2").arg(folder->path()).arg(key)), Log::WARNING);
|
||||
}
|
||||
else {
|
||||
QJsonObject valObj = val.toObject();
|
||||
@@ -301,7 +301,7 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
if (!valObj["url"].isString()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS Feed '%1'. URL is required.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
.arg(QString("%1\\%2").arg(folder->path()).arg(key)), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,11 +69,11 @@ class AsyncFileStorage;
|
||||
|
||||
namespace RSS
|
||||
{
|
||||
class Item;
|
||||
class Feed;
|
||||
class Folder;
|
||||
class Item;
|
||||
|
||||
class Session : public QObject
|
||||
class Session: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Session)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,6 +24,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "scanfoldersmodel.h"
|
||||
@@ -54,7 +56,7 @@ struct ScanFoldersModel::PathData
|
||||
QString downloadPath; // valid for CUSTOM_LOCATION
|
||||
};
|
||||
|
||||
ScanFoldersModel *ScanFoldersModel::m_instance = nullptr;
|
||||
ScanFoldersModel *ScanFoldersModel::m_instance = 0;
|
||||
|
||||
bool ScanFoldersModel::initInstance(QObject *parent)
|
||||
{
|
||||
@@ -70,7 +72,7 @@ void ScanFoldersModel::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
m_instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +83,10 @@ ScanFoldersModel *ScanFoldersModel::instance()
|
||||
|
||||
ScanFoldersModel::ScanFoldersModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_fsWatcher(nullptr)
|
||||
, m_fsWatcher(0)
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &ScanFoldersModel::configure);
|
||||
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
|
||||
}
|
||||
|
||||
ScanFoldersModel::~ScanFoldersModel()
|
||||
@@ -220,7 +222,7 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
||||
|
||||
if (!m_fsWatcher) {
|
||||
m_fsWatcher = new FileSystemWatcher(this);
|
||||
connect(m_fsWatcher, &FileSystemWatcher::torrentsAdded, this, &ScanFoldersModel::addTorrentsToSession);
|
||||
connect(m_fsWatcher, SIGNAL(torrentsAdded(const QStringList &)), this, SLOT(addTorrentsToSession(const QStringList &)));
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
@@ -233,7 +235,7 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
||||
return Ok;
|
||||
}
|
||||
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::updatePath(const QString &watchPath, const PathType &downloadType, const QString &downloadPath)
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::updatePath(const QString &watchPath, const PathType& downloadType, const QString &downloadPath)
|
||||
{
|
||||
QDir watchDir(watchPath);
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
@@ -338,7 +340,7 @@ void ScanFoldersModel::makePersistent()
|
||||
|
||||
void ScanFoldersModel::configure()
|
||||
{
|
||||
const QVariantHash dirs = Preferences::instance()->getScanDirs();
|
||||
QVariantHash dirs = Preferences::instance()->getScanDirs();
|
||||
|
||||
for (QVariantHash::const_iterator i = dirs.begin(), e = dirs.end(); i != e; ++i) {
|
||||
if (i.value().type() == QVariant::Int)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez <chris@qbittorrent.org>
|
||||
* Copyright (C) 2010 Christian Kandeler, Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -24,6 +24,8 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef SCANFOLDERSMODEL_H
|
||||
@@ -35,7 +37,7 @@
|
||||
class QStringList;
|
||||
class FileSystemWatcher;
|
||||
|
||||
class ScanFoldersModel : public QAbstractListModel
|
||||
class ScanFoldersModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ScanFoldersModel)
|
||||
@@ -64,9 +66,9 @@ public:
|
||||
CUSTOM_LOCATION
|
||||
};
|
||||
|
||||
static bool initInstance(QObject *parent = nullptr);
|
||||
static bool initInstance(QObject *parent = 0);
|
||||
static void freeInstance();
|
||||
static ScanFoldersModel *instance();
|
||||
static ScanFoldersModel* instance();
|
||||
|
||||
static QString pathTypeDisplayName(const PathType type);
|
||||
|
||||
@@ -95,7 +97,7 @@ private slots:
|
||||
void addTorrentsToSession(const QStringList &pathList);
|
||||
|
||||
private:
|
||||
explicit ScanFoldersModel(QObject *parent = nullptr);
|
||||
explicit ScanFoldersModel(QObject *parent = 0);
|
||||
~ScanFoldersModel();
|
||||
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "searchdownloadhandler.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include "../utils/fs.h"
|
||||
#include "../utils/misc.h"
|
||||
#include "searchpluginmanager.h"
|
||||
|
||||
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager)
|
||||
: QObject {manager}
|
||||
, m_manager {manager}
|
||||
, m_downloadProcess {new QProcess {this}}
|
||||
{
|
||||
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
connect(m_downloadProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished)
|
||||
, this, &SearchDownloadHandler::downloadProcessFinished);
|
||||
const QStringList params {
|
||||
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2dl.py"),
|
||||
siteUrl,
|
||||
url
|
||||
};
|
||||
// Launch search
|
||||
m_downloadProcess->start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
|
||||
}
|
||||
|
||||
void SearchDownloadHandler::downloadProcessFinished(int exitcode)
|
||||
{
|
||||
QString path;
|
||||
|
||||
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)
|
||||
path = parts[0].toString();
|
||||
}
|
||||
|
||||
emit downloadFinished(path);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QProcess;
|
||||
class SearchPluginManager;
|
||||
|
||||
class SearchDownloadHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SearchDownloadHandler)
|
||||
|
||||
friend class SearchPluginManager;
|
||||
|
||||
SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager);
|
||||
|
||||
signals:
|
||||
void downloadFinished(const QString &path);
|
||||
|
||||
private:
|
||||
void downloadProcessFinished(int exitcode);
|
||||
|
||||
SearchPluginManager *m_manager;
|
||||
QProcess *m_downloadProcess;
|
||||
};
|
||||
@@ -1,199 +0,0 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "searchhandler.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
|
||||
#include "../utils/fs.h"
|
||||
#include "../utils/misc.h"
|
||||
#include "searchpluginmanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum SearchResultColumn
|
||||
{
|
||||
PL_DL_LINK,
|
||||
PL_NAME,
|
||||
PL_SIZE,
|
||||
PL_SEEDS,
|
||||
PL_LEECHS,
|
||||
PL_ENGINE_URL,
|
||||
PL_DESC_LINK,
|
||||
NB_PLUGIN_COLUMNS
|
||||
};
|
||||
}
|
||||
|
||||
SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
|
||||
: QObject {manager}
|
||||
, m_pattern {pattern}
|
||||
, m_category {category}
|
||||
, m_usedPlugins {usedPlugins}
|
||||
, m_manager {manager}
|
||||
, m_searchProcess {new QProcess {this}}
|
||||
, m_searchTimeout {new QTimer {this}}
|
||||
{
|
||||
// Load environment variables (proxy)
|
||||
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
|
||||
const QStringList params {
|
||||
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
|
||||
m_usedPlugins.join(","),
|
||||
m_category
|
||||
};
|
||||
|
||||
// Launch search
|
||||
m_searchProcess->setProgram(Utils::Misc::pythonExecutable());
|
||||
m_searchProcess->setArguments(params + m_pattern.split(" "));
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
|
||||
#else
|
||||
connect(m_searchProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error)
|
||||
, this, &SearchHandler::processFailed);
|
||||
#endif
|
||||
connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchHandler::readSearchOutput);
|
||||
connect(m_searchProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished)
|
||||
, this, &SearchHandler::processFinished);
|
||||
|
||||
m_searchTimeout->setSingleShot(true);
|
||||
connect(m_searchTimeout, &QTimer::timeout, this, &SearchHandler::cancelSearch);
|
||||
m_searchTimeout->start(180000); // 3 min
|
||||
|
||||
// deferred start allows clients to handle starting-related signals
|
||||
QTimer::singleShot(0, this, [this]() { m_searchProcess->start(QIODevice::ReadOnly); });
|
||||
}
|
||||
|
||||
bool SearchHandler::isActive() const
|
||||
{
|
||||
return (m_searchProcess->state() != QProcess::NotRunning);
|
||||
}
|
||||
|
||||
void SearchHandler::cancelSearch()
|
||||
{
|
||||
if ((m_searchProcess->state() == QProcess::NotRunning) || m_searchCancelled)
|
||||
return;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
m_searchProcess->kill();
|
||||
#else
|
||||
m_searchProcess->terminate();
|
||||
#endif
|
||||
m_searchCancelled = true;
|
||||
m_searchTimeout->stop();
|
||||
}
|
||||
|
||||
// Slot called when QProcess is Finished
|
||||
// QProcess can be finished for 3 reasons:
|
||||
// Error | Stopped by user | Finished normally
|
||||
void SearchHandler::processFinished(int exitcode)
|
||||
{
|
||||
m_searchTimeout->stop();
|
||||
|
||||
if (m_searchCancelled)
|
||||
emit searchFinished(true);
|
||||
else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0))
|
||||
emit searchFinished(false);
|
||||
else
|
||||
emit searchFailed();
|
||||
}
|
||||
|
||||
// search QProcess return output as soon as it gets new
|
||||
// stuff to read. We split it into lines and parse each
|
||||
// line to SearchResult calling parseSearchResult().
|
||||
void SearchHandler::readSearchOutput()
|
||||
{
|
||||
QByteArray output = m_searchProcess->readAllStandardOutput();
|
||||
output.replace("\r", "");
|
||||
QList<QByteArray> lines = output.split('\n');
|
||||
if (!m_searchResultLineTruncated.isEmpty())
|
||||
lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
|
||||
m_searchResultLineTruncated = lines.takeLast().trimmed();
|
||||
|
||||
QList<SearchResult> searchResultList;
|
||||
foreach (const QByteArray &line, lines) {
|
||||
SearchResult searchResult;
|
||||
if (parseSearchResult(QString::fromUtf8(line), searchResult))
|
||||
searchResultList << searchResult;
|
||||
}
|
||||
|
||||
if (!searchResultList.isEmpty()) {
|
||||
m_results.append(searchResultList);
|
||||
emit newSearchResults(searchResultList);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchHandler::processFailed()
|
||||
{
|
||||
if (!m_searchCancelled)
|
||||
emit searchFailed();
|
||||
}
|
||||
|
||||
// Parse one line of search results list
|
||||
// Line is in the following form:
|
||||
// file url | file name | file size | nb seeds | nb leechers | Search engine url
|
||||
bool SearchHandler::parseSearchResult(const QString &line, SearchResult &searchResult)
|
||||
{
|
||||
const QStringList parts = line.split("|");
|
||||
const int nbFields = parts.size();
|
||||
if (nbFields < (NB_PLUGIN_COLUMNS - 1)) return false; // -1 because desc_link is optional
|
||||
|
||||
searchResult = SearchResult();
|
||||
searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed(); // download URL
|
||||
searchResult.fileName = parts.at(PL_NAME).trimmed(); // Name
|
||||
searchResult.fileSize = parts.at(PL_SIZE).trimmed().toLongLong(); // Size
|
||||
bool ok = false;
|
||||
searchResult.nbSeeders = parts.at(PL_SEEDS).trimmed().toLongLong(&ok); // Seeders
|
||||
if (!ok || (searchResult.nbSeeders < 0))
|
||||
searchResult.nbSeeders = -1;
|
||||
searchResult.nbLeechers = parts.at(PL_LEECHS).trimmed().toLongLong(&ok); // Leechers
|
||||
if (!ok || (searchResult.nbLeechers < 0))
|
||||
searchResult.nbLeechers = -1;
|
||||
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed(); // Search site URL
|
||||
if (nbFields == NB_PLUGIN_COLUMNS)
|
||||
searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed(); // Description Link
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SearchPluginManager *SearchHandler::manager() const
|
||||
{
|
||||
return m_manager;
|
||||
}
|
||||
|
||||
QList<SearchResult> SearchHandler::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
QString SearchHandler::pattern() const
|
||||
{
|
||||
return m_pattern;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user