mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-03 14:12:30 -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
|
cp "version" $HOME/hombebrew_cache
|
||||||
cd "$HOME/hombebrew_cache"
|
cd "$HOME/hombebrew_cache"
|
||||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb
|
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
|
fi
|
||||||
|
|
||||||
# Copy custom libtorrent bottle to homebrew's cache so it can find and install it
|
# 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
|
# Also install our custom libtorrent formula by passing the local path to it
|
||||||
# These 2 files are restored from Travis' cache.
|
# 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"
|
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||||
|
|
||||||
if [ "$build_system" = "cmake" ]; then
|
if [ "$build_system" = "cmake" ]; then
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
cmake_policy(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)
|
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
|
||||||
include(FunctionReadVersion)
|
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(WEBUI "Allows to disable the WebUI." ON)
|
||||||
|
|
||||||
option(STACKTRACE "Enable stacktrace feature" ON)
|
if (WIN32)
|
||||||
|
option(STACKTRACE_WIN "")
|
||||||
if (UNIX)
|
else (WIN32)
|
||||||
cmake_dependent_option(SYSTEMD "Install the systemd service file (headless only)" OFF
|
cmake_dependent_option(SYSTEMD "Install the systemd service file (headless only)" OFF
|
||||||
"NOT GUI" OFF)
|
"NOT GUI" OFF)
|
||||||
cmake_dependent_option(DBUS "Enable use of QtDBus (GUI only)" ON "GUI" OFF)
|
cmake_dependent_option(DBUS "Enable use of QtDBus (GUI only)" ON "GUI" OFF)
|
||||||
endif(UNIX)
|
endif(WIN32)
|
||||||
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
All new code **must** follow the following coding guidelines.
|
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.
|
If you make changes in a file that still uses another coding style, make sure that you follow these guidelines for your changes instead.
|
||||||
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.
|
|
||||||
|
|
||||||
**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 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 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.
|
**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 ####
|
#### a. Function blocks, class/struct definitions, namespaces ####
|
||||||
```c++
|
```c++
|
||||||
int myFunction(int a)
|
int myFunction(int a)
|
||||||
@@ -92,8 +89,18 @@ default:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### d. If-else statements ####
|
#### d. Brace enclosed initializers ####
|
||||||
The `else if`/`else` must be on their own lines:
|
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++
|
```c++
|
||||||
if (condition) {
|
if (condition) {
|
||||||
// code
|
// code
|
||||||
@@ -105,71 +112,40 @@ else {
|
|||||||
// code
|
// code
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
The `else if`/`else` must be on their own lines.
|
||||||
|
|
||||||
#### e. Single statement if blocks ####
|
#### b. Single statement if blocks ####
|
||||||
Most single statement if blocks should look like this:
|
**Most** single statement if blocks should look like this:
|
||||||
```c++
|
```c++
|
||||||
if (condition)
|
if (condition)
|
||||||
a = a + b;
|
a = a + b;
|
||||||
```
|
```
|
||||||
|
|
||||||
One acceptable exception to this can be `return`, `break` or `continue` statements,
|
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.
|
||||||
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.
|
|
||||||
```c++
|
```c++
|
||||||
if (a > 0) return;
|
a = myFunction();
|
||||||
|
b = a * 1500;
|
||||||
|
|
||||||
while (p) {
|
if (b > 0) return;
|
||||||
// ...
|
c = 100 / b;
|
||||||
if (!b) continue;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### f. Acceptable conditions to omit braces ####
|
#### c. Using curly braces for single statement if blocks ####
|
||||||
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
|
|
||||||
|
|
||||||
if (a < b)
|
However, there are cases where curly braces for single statement if blocks **should** be used.
|
||||||
do(a);
|
* 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.
|
||||||
else if (a > b)
|
* Another exception would be when we have nested if blocks or generally multiple levels of code that affect code readability.
|
||||||
do(b);
|
|
||||||
else
|
|
||||||
do(c);
|
|
||||||
|
|
||||||
if (a < b) {
|
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.
|
||||||
do(a);
|
|
||||||
}
|
|
||||||
else if (a > b) { // curly braces required here, then all branches should also add them
|
|
||||||
do(b);
|
|
||||||
do(d);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
do(c);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### g. Brace enclosed initializers ####
|
### 3. Indentation ###
|
||||||
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 ###
|
|
||||||
4 spaces.
|
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.
|
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.
|
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++
|
```c++
|
||||||
myClass::myClass(int a, int b, int c, int d)
|
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.
|
Enums should be vertical. This will allow for more easily readable diffs. The members should be indented.
|
||||||
```c++
|
```c++
|
||||||
enum Days
|
enum Days
|
||||||
@@ -197,7 +173,7 @@ enum Days
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Names. ###
|
### 7. Names. ###
|
||||||
All names should be camelCased.
|
All names should be camelCased.
|
||||||
|
|
||||||
#### a. Type names and namespaces ####
|
#### a. Type names and namespaces ####
|
||||||
@@ -231,73 +207,40 @@ class MyClass
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. Header inclusion order. ###
|
### 8. Header inclusion order. ###
|
||||||
The headers should be placed in the following group order:
|
The headers should be placed in the following order:
|
||||||
1. Module header (in .cpp)
|
1. Module header (in .cpp)
|
||||||
2. C++ Standard Library headers
|
2. System/Qt/Boost etc. headers (splitted in subcategories if you have many).
|
||||||
3. System headers
|
3. Application headers, starting from *Base* 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.
|
|
||||||
|
|
||||||
|
The headers should be ordered alphabetically within each group (subgroup).<br/>
|
||||||
|
<br/>
|
||||||
Example:
|
Example:
|
||||||
```c++
|
```c++
|
||||||
// file: examplewidget.cpp
|
// examplewidget.cpp
|
||||||
|
|
||||||
// Module header
|
|
||||||
#include "examplewidget.h"
|
#include "examplewidget.h"
|
||||||
|
|
||||||
// exceptions, headers containing version number
|
#include <cmath>
|
||||||
#include <boost/version.hpp>
|
|
||||||
#include <libtorrent/version.hpp>
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
// C++ Standard Library headers
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN // conditional
|
#include <QDateTime>
|
||||||
#include <cmath>
|
#include <QList>
|
||||||
#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 <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#ifdef Q_OS_MAC // conditional
|
#include <libtorrent/version.hpp>
|
||||||
#include <QFont>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 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 "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"
|
#include "ui_examplewidget.h"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8. Include guard. ###
|
### 9. Include guard. ###
|
||||||
`#pragma once` should be used instead of "include guard" in new code:
|
`#pragma once` should be used instead of "include guard" in new code:
|
||||||
```c++
|
```c++
|
||||||
// examplewidget.h
|
// examplewidget.h
|
||||||
@@ -313,7 +256,7 @@ class ExampleWidget : public QWidget
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9. Misc. ###
|
### 10. Misc. ###
|
||||||
|
|
||||||
* Line breaks for long lines with operation:
|
* Line breaks for long lines with operation:
|
||||||
|
|
||||||
@@ -389,16 +332,5 @@ i++, j--; // No
|
|||||||
|
|
||||||
* Method definitions aren't allowed in header files
|
* Method definitions aren't allowed in header files
|
||||||
|
|
||||||
### 10. Git commit message ###
|
### 10. Not covered above ###
|
||||||
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)
|
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.
|
||||||
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.*
|
|
||||||
|
|||||||
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
|
# Opening a pull request
|
||||||
|
|
||||||
### Must read
|
### 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].
|
* 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.
|
* Keep the title **short** and provide a **clear** description about what your pull request does.
|
||||||
* Provide **screenshots** for UI related changes.
|
* 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].
|
* Keep your git commit history **clean** and **precise**. Commits like `xxx fixup` should not appear.
|
||||||
* 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].
|
* 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
|
### 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.
|
* **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][merging-vs-rebasing-url].
|
* 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).
|
||||||
|
|
||||||
[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/
|
|
||||||
|
|||||||
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
|
* Fri Feb 16 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.0.4
|
||||||
- FEATURE: Add source field in Torrent creator. Closes #7965. (Chocobo1)
|
- FEATURE: Add source field in Torrent creator. Closes #7965. (Chocobo1)
|
||||||
- FEATURE: Torrent creator: raise maximum piece size to 32 MiB (Chocobo1)
|
- FEATURE: Torrent creator: raise maximum piece size to 32 MiB (Chocobo1)
|
||||||
@@ -222,7 +122,7 @@
|
|||||||
- BUGFIX: Optimize code for SpeedWidget. (dzmat)
|
- BUGFIX: Optimize code for SpeedWidget. (dzmat)
|
||||||
- BUGFIX: Disable processing events when adding torrents(prevents crashes). Closes #7436. (Chocobo1)
|
- BUGFIX: Disable processing events when adding torrents(prevents crashes). Closes #7436. (Chocobo1)
|
||||||
- BUGFIX: Open links in browser. Closes #7651. (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: Explicitly set UPnP state on start-up. Closes #7338. (Chocobo1)
|
||||||
- BUGFIX: Include/print caught signal in stackdump (Chocobo1)
|
- BUGFIX: Include/print caught signal in stackdump (Chocobo1)
|
||||||
- COSMETIC: Trackerlist: Set text alignment of columns with numbers to the right (thalieht)
|
- 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: Use Ctrl+F to search torrents. Closes #5797. (Tim Delaney)
|
||||||
- FEATURE: Transferlist: add hotkeys for double click and recheck selected torrents (thalieht)
|
- FEATURE: Transferlist: add hotkeys for double click and recheck selected torrents (thalieht)
|
||||||
- FEATURE: Add hotkey for execution log tab, Trackerlist, Peerlist etc (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: Do not remove added files unconditionally. Closes #6248 (Eugene Shalygin)
|
||||||
- BUGFIX: Ignore mouse wheel events in Advanced Settings. Closes #866. (Chocobo1)
|
- 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)
|
- 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: 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: 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: 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: Fixed sort order for datetime columns with empty values (closes #2988) (Vladimir Sinenko)
|
||||||
- BUGFIX: Disable proxy in WebUI HTTP server. Closes #6349. (Eugene Shalygin)
|
- BUGFIX: Disable proxy in WebUI HTTP server. Closes #6349. (Eugene Shalygin)
|
||||||
- COSMETIC: Use a disabled progressbar's palette for unselected files. (sledgehammer999)
|
- 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 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: 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: 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)
|
- 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)
|
- PERFORMANCE: Optimize drawing in speed graph (Anton Lashkov, Chocobo1)
|
||||||
- BUGFIX: Fix memory leak. (sledgehammer999)
|
- BUGFIX: Fix memory leak. (sledgehammer999)
|
||||||
- BUGFIX: Fix resizing bug in "add torrent dialog". Closes #5036. (Chocobo1)
|
- 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: 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 empty trackers (ngosang)
|
||||||
- BUGFIX: Fix Add tracker dialog URL download (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: 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: Delete Import Torrent Dialog. Just use the "add new torrent" dialog. (glassez)
|
||||||
- OTHER: Optimize code for natural sorting (Chocobo1)
|
- 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)
|
- OTHER: Fix gcc 6 compilation with qmake. See #5237. (sledgehammer999)
|
||||||
|
|
||||||
* Tue Mar 29 2016 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.4
|
* Tue Mar 29 2016 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.4
|
||||||
@@ -875,7 +775,7 @@
|
|||||||
- SEARCH: Fix thepiratebay. Closes #3012 (ngosang)
|
- SEARCH: Fix thepiratebay. Closes #3012 (ngosang)
|
||||||
- SEARCH: Improve torrentz engine to return more results (ngosang)
|
- SEARCH: Improve torrentz engine to return more results (ngosang)
|
||||||
- SEARCH: Change width of columns in search tab. Closes #764 (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: Aborting search engine process during closure. Close #2671 (DoumanAsh)
|
||||||
- SEARCH: Perform searches in parallel (DoumanAsh)
|
- SEARCH: Perform searches in parallel (DoumanAsh)
|
||||||
- SEARCH: Add Demonoid search engine (ngosang)
|
- SEARCH: Add Demonoid search engine (ngosang)
|
||||||
@@ -1042,7 +942,7 @@
|
|||||||
- WEBUI: Removed broken 'Documentation'. Improves fix for #1343 (Benjamin Hutchins)
|
- 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)
|
- 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 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: 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 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)
|
- 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: Show external IP in the log. Closes #968. (sledgehammer999)
|
||||||
- FEATURE: Enable gzip compression in the webui. It should be faster now. (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: 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: Allow to clear the UI lock password. Closes #973. (sledgehammer999)
|
||||||
- FEATURE: New translations: English(Australia) and English(United Kingdom)
|
- FEATURE: New translations: English(Australia) and English(United Kingdom)
|
||||||
- BUGFIX: Expose all available translation in the WebUI. Closes #976. (sledgehammer999)
|
- 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: Make peer tab sortable by ip too (Gelmir)
|
||||||
- OTHER: Translations moved to Transifex(https://www.transifex.com/projects/p/qbittorrent/)
|
- OTHER: Translations moved to Transifex(https://www.transifex.com/projects/p/qbittorrent/)
|
||||||
- OTHER: New Translation - Vietnamese (Anh Phan)
|
- OTHER: New Translation - Vietnamese (Anh Phan)
|
||||||
- PERFORMANCE: Improve drawing speed of tranferlist when there are many torrents(>100)
|
- PERFORMANCE: Impove 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 peers list when there are many peers
|
||||||
|
|
||||||
* Mon Jul 29 2013 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.0.11
|
* Mon Jul 29 2013 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.0.11
|
||||||
- FEATURE: Allow more fine tuning of upload slots. It should improve speed (sledgehammer999)
|
- FEATURE: Allow more fine tuning of upload slots. It should improve speed (sledgehammer999)
|
||||||
@@ -1266,8 +1166,8 @@
|
|||||||
- LIBTORRENT: SOCKS5 fixes (0.16.10)
|
- LIBTORRENT: SOCKS5 fixes (0.16.10)
|
||||||
- LIBTORRENT: Fix hanging issue on Windows when closing files (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)
|
- 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: Impove 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 peers list when there are many peers (sledgehammer999)
|
||||||
|
|
||||||
* Sat Mar 16 2013 - Christophe Dumez <chris@qbittorrent.org> - v3.0.9
|
* Sat Mar 16 2013 - Christophe Dumez <chris@qbittorrent.org> - v3.0.9
|
||||||
- BUGFIX: Raise qBittorrent windows when another instance is launched
|
- 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: Fix "Couldn't set environment variable..." message on start up (closes #245)
|
||||||
- BUGFIX: Use right path separator in torrent addition dialog on Windows
|
- BUGFIX: Use right path separator in torrent addition dialog on Windows
|
||||||
- BUGFIX: Fix "Set as default save path" setting (closes #254)
|
- 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: Fixed several search engine plugins and removed the dead ones
|
||||||
- BUGFIX: Use https links in search plugins when possible
|
- BUGFIX: Use https links in search plugins when possible
|
||||||
- BUGFIX: Bump Mootools to v1.4.5 (Web UI)
|
- BUGFIX: Bump Mootools to v1.4.5 (Web UI)
|
||||||
@@ -1427,7 +1327,7 @@
|
|||||||
- I18N: Add Georgian translation
|
- I18N: Add Georgian translation
|
||||||
|
|
||||||
* Sat Oct 29 2011 - Christophe Dumez <chris@qbittorrent.org> - v2.9.2
|
* 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-datetime
|
||||||
- BUGFIX: Remove dependency on boost-filesystem (libtorrent v0.16.x)
|
- 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: Update RSS feed as soon as feed downloader is enabled
|
||||||
- BUGFIX: RSS Feed downloader ignores articles above maximum number of articles
|
- BUGFIX: RSS Feed downloader ignores articles above maximum number of articles
|
||||||
- BUGFIX: Fix possible bug when deleting a RSS folder
|
- 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: RSS filters are now alphabetically sorted
|
||||||
- BUGFIX: Fix crash when renaming currently displayed RSS filter
|
- BUGFIX: Fix crash when renaming currently displayed RSS filter
|
||||||
- BUGFIX: Remove overwriting confirmation when exporting RSS filters since Qt takes care of it
|
- 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
|
- 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
|
* 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: Fix RSS Feed downloader for some feeds
|
||||||
- BUGFIX: Do not use home folder as a fallback when the save path is not accessible
|
- BUGFIX: Do not use home folder as a fallback when the save path is not accessible
|
||||||
- BUGFIX: Fix Mininova, ThePirateBay search engine plugins
|
- BUGFIX: Fix Mininova, ThePirateBay search engine plugins
|
||||||
@@ -1764,7 +1664,7 @@
|
|||||||
- BUGFIX: Fix trackers addition to torrents (bug introduced in v1.5.4)
|
- BUGFIX: Fix trackers addition to torrents (bug introduced in v1.5.4)
|
||||||
- BUGFIX: Suppress compilation warning regarding sortNewsList() not being used
|
- 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: 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
|
* Sun Oct 25 2009 - Christophe Dumez <chris@qbittorrent.org> - v1.5.4
|
||||||
- BUGFIX: Updated man page
|
- BUGFIX: Updated man page
|
||||||
@@ -1831,7 +1731,7 @@
|
|||||||
- FEATURE: Added right click menu in search engine to clear completion history
|
- 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: Allow to set a different port for DHT (UDP) than the one used for Bittorrent
|
||||||
- FEATURE: Updated spoofing code to avoid trackers ban
|
- 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
|
- BUGFIX: Stop enforcing UTF-8 and use system locale instead
|
||||||
- COSMETIC: Redesigned program preferences
|
- COSMETIC: Redesigned program preferences
|
||||||
- COSMETIC: Updated icons set
|
- 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: 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 a little bug in search engine plugins helper file
|
||||||
- BUGFIX: Fixed compilation problems with Qt 4.3
|
- 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: Cleanly fixed popup menus position in lists (no more workarounds)
|
||||||
- BUGFIX: Fixed memory leak in search engine
|
- BUGFIX: Fixed memory leak in search engine
|
||||||
- BUGFIX: Torrents with an infinite ratio are no longer affected by ratio_limit set in program preferences
|
- 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: Number of complete/incomplete sources are now displayed in download list for each torrent
|
||||||
- FEATURE: Implemented close to systray
|
- FEATURE: Implemented close to systray
|
||||||
- FEATURE: Added Autocompletion to search engine
|
- 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: New parameters for configure file to point to custom locations for libtorrent/libcurl
|
||||||
- FEATURE: Update application style according to the system (WindowsXP, MacOS, X11)
|
- 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)
|
- 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
|
# - 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
|
# and the macro links Qt4 ones if QT4_FOUND is set or Qt5 ones if not
|
||||||
|
|
||||||
macro (target_link_qt_components target)
|
macro (target_link_qt_components target)
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ macro(qbt_set_compiler_options)
|
|||||||
#-Wshadow -Wconversion ?
|
#-Wshadow -Wconversion ?
|
||||||
set(_GCC_COMMON_C_AND_CXX_FLAGS "-Wall -Wextra"
|
set(_GCC_COMMON_C_AND_CXX_FLAGS "-Wall -Wextra"
|
||||||
"-Wfloat-equal -Wcast-qual -Wcast-align"
|
"-Wfloat-equal -Wcast-qual -Wcast-align"
|
||||||
"-Wsign-conversion -Winvalid-pch -Wno-long-long"
|
"-Wsign-conversion -Winvalid-pch -Werror=return-type -Wno-long-long"
|
||||||
#"-fstack-protector-all"
|
# -fstack-protector-all
|
||||||
#"-Werror -Wno-error=deprecated-declarations"
|
"-Werror -Wno-error=deprecated-declarations"
|
||||||
)
|
)
|
||||||
set(_GCC_COMMON_CXX_FLAGS "-fexceptions -frtti"
|
set (_GCC_COMMON_CXX_FLAGS "-fexceptions -frtti"
|
||||||
"-Woverloaded-virtual -Wold-style-cast"
|
"-Woverloaded-virtual -Wold-style-cast -Wstrict-null-sentinel"
|
||||||
"-Wnon-virtual-dtor -Wfloat-equal -Wcast-qual -Wcast-align"
|
"-Wnon-virtual-dtor -Wfloat-equal -Wcast-qual -Wcast-align"
|
||||||
#"-Weffc++"
|
"-Werror=overloaded-virtual"
|
||||||
#"-Werror -Wno-error=cpp"
|
# "-Weffc++"
|
||||||
|
"-Werror -Wno-error=cpp"
|
||||||
# we should modify code to make these ones obsolete
|
# 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)
|
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(${GLIBC_VERSION})
|
||||||
endif (CMAKE_SYSTEM_NAME MATCHES Linux)
|
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_C_AND_CXX_FLAGS_STRING "${_GCC_COMMON_C_AND_CXX_FLAGS}")
|
||||||
string(REPLACE ";" " " _GCC_COMMON_CXX_FLAGS_STRING "${_GCC_COMMON_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
|
#DEFINES += TORRENT_LINKING_SHARED
|
||||||
|
|
||||||
# Enable stack trace support
|
# Enable stack trace support
|
||||||
CONFIG += stacktrace
|
CONFIG += strace_win
|
||||||
|
|||||||
68
configure
vendored
68
configure
vendored
@@ -1,6 +1,6 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
# Guess values for system-dependent variables and create Makefiles.
|
# 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>.
|
# Report bugs to <bugs.qbittorrent.org>.
|
||||||
#
|
#
|
||||||
@@ -580,8 +580,8 @@ MAKEFLAGS=
|
|||||||
# Identity of this package.
|
# Identity of this package.
|
||||||
PACKAGE_NAME='qbittorrent'
|
PACKAGE_NAME='qbittorrent'
|
||||||
PACKAGE_TARNAME='qbittorrent'
|
PACKAGE_TARNAME='qbittorrent'
|
||||||
PACKAGE_VERSION='v4.1.1'
|
PACKAGE_VERSION='v4.0.4'
|
||||||
PACKAGE_STRING='qbittorrent v4.1.1'
|
PACKAGE_STRING='qbittorrent v4.0.4'
|
||||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||||
|
|
||||||
@@ -717,7 +717,6 @@ enable_dependency_tracking
|
|||||||
enable_silent_rules
|
enable_silent_rules
|
||||||
with_qtsingleapplication
|
with_qtsingleapplication
|
||||||
enable_debug
|
enable_debug
|
||||||
enable_stacktrace
|
|
||||||
enable_gui
|
enable_gui
|
||||||
enable_systemd
|
enable_systemd
|
||||||
enable_webui
|
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.
|
# 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.
|
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||||
cat <<_ACEOF
|
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]...
|
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||||
|
|
||||||
@@ -1368,7 +1367,7 @@ fi
|
|||||||
|
|
||||||
if test -n "$ac_init_help"; then
|
if test -n "$ac_init_help"; then
|
||||||
case $ac_init_help in
|
case $ac_init_help in
|
||||||
short | recursive ) echo "Configuration of qbittorrent v4.1.1:";;
|
short | recursive ) echo "Configuration of qbittorrent v4.0.4:";;
|
||||||
esac
|
esac
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
|
|
||||||
@@ -1383,7 +1382,6 @@ Optional Features:
|
|||||||
--enable-silent-rules less verbose build output (undo: "make V=1")
|
--enable-silent-rules less verbose build output (undo: "make V=1")
|
||||||
--disable-silent-rules verbose build output (undo: "make V=0")
|
--disable-silent-rules verbose build output (undo: "make V=0")
|
||||||
--enable-debug Enable debug build
|
--enable-debug Enable debug build
|
||||||
--enable-stacktrace Enable stacktrace feature (default=auto)
|
|
||||||
--disable-gui Disable the GUI for headless running. Disables
|
--disable-gui Disable the GUI for headless running. Disables
|
||||||
QtDBus and the GeoIP Database.
|
QtDBus and the GeoIP Database.
|
||||||
--enable-systemd Install the systemd service file (headless only).
|
--enable-systemd Install the systemd service file (headless only).
|
||||||
@@ -1503,7 +1501,7 @@ fi
|
|||||||
test -n "$ac_init_help" && exit $ac_status
|
test -n "$ac_init_help" && exit $ac_status
|
||||||
if $ac_init_version; then
|
if $ac_init_version; then
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
qbittorrent configure v4.1.1
|
qbittorrent configure v4.0.4
|
||||||
generated by GNU Autoconf 2.69
|
generated by GNU Autoconf 2.69
|
||||||
|
|
||||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||||
@@ -1642,7 +1640,7 @@ cat >config.log <<_ACEOF
|
|||||||
This file contains any messages produced by compilers while
|
This file contains any messages produced by compilers while
|
||||||
running configure, to aid debugging if configure makes a mistake.
|
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
|
generated by GNU Autoconf 2.69. Invocation command line was
|
||||||
|
|
||||||
$ $0 $@
|
$ $0 $@
|
||||||
@@ -3820,7 +3818,7 @@ fi
|
|||||||
|
|
||||||
# Define the identity of the package.
|
# Define the identity of the package.
|
||||||
PACKAGE='qbittorrent'
|
PACKAGE='qbittorrent'
|
||||||
VERSION='v4.1.1'
|
VERSION='v4.0.4'
|
||||||
|
|
||||||
|
|
||||||
cat >>confdefs.h <<_ACEOF
|
cat >>confdefs.h <<_ACEOF
|
||||||
@@ -4191,14 +4189,6 @@ else
|
|||||||
fi
|
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.
|
# Check whether --enable-gui was given.
|
||||||
if test "${enable_gui+set}" = set; then :
|
if test "${enable_gui+set}" = set; then :
|
||||||
enableval=$enable_gui;
|
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 ;;
|
as_fn_error $? "Unknown option \"$enable_debug\". Use either \"yes\" or \"no\"." "$LINENO" 5 ;;
|
||||||
esac
|
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 "$as_me:${as_lineno-$LINENO}: checking whether to enable the GUI" >&5
|
||||||
$as_echo_n "checking whether to enable the GUI... " >&6; }
|
$as_echo_n "checking whether to enable the GUI... " >&6; }
|
||||||
case "x$enable_gui" in #(
|
case "x$enable_gui" in #(
|
||||||
@@ -4679,6 +4636,7 @@ esac
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Check whether --with-boost was given.
|
# Check whether --with-boost was given.
|
||||||
if test "${with_boost+set}" = set; then :
|
if test "${with_boost+set}" = set; then :
|
||||||
withval=$with_boost;
|
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
|
# report actual input values of CONFIG_FILES etc. instead of their
|
||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
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
|
generated by GNU Autoconf 2.69. Invocation command line was
|
||||||
|
|
||||||
CONFIG_FILES = $CONFIG_FILES
|
CONFIG_FILES = $CONFIG_FILES
|
||||||
@@ -6198,7 +6156,7 @@ _ACEOF
|
|||||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
qbittorrent config.status v4.1.1
|
qbittorrent config.status v4.0.4
|
||||||
configured by $0, generated by GNU Autoconf 2.69,
|
configured by $0, generated by GNU Autoconf 2.69,
|
||||||
with options \\"\$ac_cs_config\\"
|
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
|
# report actual input values of CONFIG_FILES etc. instead of their
|
||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
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
|
generated by GNU Autoconf 2.69. Invocation command line was
|
||||||
|
|
||||||
CONFIG_FILES = $CONFIG_FILES
|
CONFIG_FILES = $CONFIG_FILES
|
||||||
@@ -7513,7 +7471,7 @@ _ACEOF
|
|||||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
qbittorrent config.status v4.1.1
|
qbittorrent config.status v4.0.4
|
||||||
configured by $0, generated by GNU Autoconf 2.69,
|
configured by $0, generated by GNU Autoconf 2.69,
|
||||||
with options \\"\$ac_cs_config\\"
|
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_AUX_DIR([build-aux])
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
@@ -24,12 +24,6 @@ AC_ARG_ENABLE(debug,
|
|||||||
[],
|
[],
|
||||||
[enable_debug=no])
|
[enable_debug=no])
|
||||||
|
|
||||||
AC_ARG_ENABLE(stacktrace,
|
|
||||||
[AS_HELP_STRING([--enable-stacktrace],
|
|
||||||
[Enable stacktrace feature (default=auto)])],
|
|
||||||
[],
|
|
||||||
[enable_stacktrace=auto])
|
|
||||||
|
|
||||||
AC_ARG_ENABLE(gui,
|
AC_ARG_ENABLE(gui,
|
||||||
[AS_HELP_STRING([--disable-gui],
|
[AS_HELP_STRING([--disable-gui],
|
||||||
[Disable the GUI for headless running. Disables QtDBus and the GeoIP Database.])],
|
[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_RESULT([$enable_debug])
|
||||||
AC_MSG_ERROR([Unknown option "$enable_debug". Use either "yes" or "no".])])
|
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])
|
AC_MSG_CHECKING([whether to enable the GUI])
|
||||||
AS_CASE(["x$enable_gui"],
|
AS_CASE(["x$enable_gui"],
|
||||||
["xyes"],
|
["xyes"],
|
||||||
|
|||||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -45,7 +45,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.1.1</string>
|
<string>4.0.4</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>qBit</string>
|
<string>qBit</string>
|
||||||
<key>CFBundleExecutable</key>
|
<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
|
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||||
|
|
||||||
; Program specific
|
; Program specific
|
||||||
!define PROG_VERSION "4.1.1"
|
!define PROG_VERSION "4.0.4"
|
||||||
|
|
||||||
!define MUI_FINISHPAGE_RUN
|
!define MUI_FINISHPAGE_RUN
|
||||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
!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}
|
tarball.commands += rm -fR $${PROJECT_NAME}-$${PROJECT_VERSION}
|
||||||
|
|
||||||
QMAKE_EXTRA_TARGETS += tarball
|
QMAKE_EXTRA_TARGETS += tarball
|
||||||
|
|
||||||
# For Qt Creator beautifier
|
|
||||||
DISTFILES += \
|
|
||||||
uncrustify.cfg
|
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ if (NOT WEBUI)
|
|||||||
add_definitions(-DDISABLE_WEBUI)
|
add_definitions(-DDISABLE_WEBUI)
|
||||||
endif (NOT WEBUI)
|
endif (NOT WEBUI)
|
||||||
|
|
||||||
if (STACKTRACE)
|
if (STACKTRACE_WIN)
|
||||||
add_definitions(-DSTACKTRACE)
|
add_definitions(-DSTACKTRACE_WIN)
|
||||||
endif(STACKTRACE)
|
endif(STACKTRACE_WIN)
|
||||||
# nogui {
|
# nogui {
|
||||||
# TARGET = qbittorrent-nox
|
# TARGET = qbittorrent-nox
|
||||||
# } else {
|
# } else {
|
||||||
|
|||||||
@@ -53,16 +53,16 @@ if (WIN32)
|
|||||||
list(APPEND QBT_APP_SOURCES ../qbittorrent.exe.manifest)
|
list(APPEND QBT_APP_SOURCES ../qbittorrent.exe.manifest)
|
||||||
endif (WIN32)
|
endif (WIN32)
|
||||||
|
|
||||||
if (STACKTRACE)
|
if (UNIX)
|
||||||
if (UNIX)
|
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
||||||
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
endif (UNIX)
|
||||||
else (UNIX)
|
|
||||||
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
if (STACKTRACE_WIN)
|
||||||
if (GUI)
|
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
||||||
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
if (GUI)
|
||||||
endif (GUI)
|
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
||||||
endif (UNIX)
|
endif (GUI)
|
||||||
endif (STACKTRACE)
|
endif (STACKTRACE_WIN)
|
||||||
|
|
||||||
# usesystemqtsingleapplication {
|
# usesystemqtsingleapplication {
|
||||||
# nogui {
|
# nogui {
|
||||||
|
|||||||
@@ -25,16 +25,12 @@ SOURCES += \
|
|||||||
$$PWD/filelogger.cpp \
|
$$PWD/filelogger.cpp \
|
||||||
$$PWD/main.cpp
|
$$PWD/main.cpp
|
||||||
|
|
||||||
stacktrace {
|
unix: HEADERS += $$PWD/stacktrace.h
|
||||||
unix {
|
strace_win {
|
||||||
HEADERS += $$PWD/stacktrace.h
|
HEADERS += $$PWD/stacktrace_win.h
|
||||||
}
|
!nogui {
|
||||||
else {
|
HEADERS += $$PWD/stacktrace_win_dlg.h
|
||||||
HEADERS += $$PWD/stacktrace_win.h
|
FORMS += $$PWD/stacktrace_win_dlg.ui
|
||||||
!nogui {
|
|
||||||
HEADERS += $$PWD/stacktrace_win_dlg.h
|
|
||||||
FORMS += $$PWD/stacktrace_win_dlg.ui
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,73 +27,62 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "application.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#include <memory>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QAtomicInt>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QLibraryInfo>
|
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QProcess>
|
#include <QLibraryInfo>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
|
#include <QProcess>
|
||||||
#include "base/bittorrent/session.h"
|
#include <QAtomicInt>
|
||||||
#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"
|
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
|
#include "gui/guiiconprovider.h"
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <QSessionManager>
|
#include <windows.h>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
|
#include <QSessionManager>
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
#include <QFileOpenEvent>
|
#include <QFileOpenEvent>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#endif // Q_OS_MAC
|
#endif // Q_OS_MAC
|
||||||
#include "addnewtorrentdialog.h"
|
|
||||||
#include "gui/guiiconprovider.h"
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
#include "addnewtorrentdialog.h"
|
||||||
#include "shutdownconfirmdlg.h"
|
#include "shutdownconfirmdlg.h"
|
||||||
#else // DISABLE_GUI
|
#else // DISABLE_GUI
|
||||||
#include <cstdio>
|
#include <iostream>
|
||||||
#endif // DISABLE_GUI
|
#endif // DISABLE_GUI
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#include <Shellapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef DISABLE_WEBUI
|
#ifndef DISABLE_WEBUI
|
||||||
#include "webui/webui.h"
|
#include "webui/webui.h"
|
||||||
#endif
|
#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
|
namespace
|
||||||
{
|
{
|
||||||
#define SETTINGS_KEY(name) "Application/" name
|
#define SETTINGS_KEY(name) "Application/" name
|
||||||
|
|
||||||
// FileLogger properties keys
|
// 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_ENABLED = FILELOGGER_SETTINGS_KEY("Enabled");
|
||||||
const QString KEY_FILELOGGER_PATH = FILELOGGER_SETTINGS_KEY("Path");
|
const QString KEY_FILELOGGER_PATH = FILELOGGER_SETTINGS_KEY("Path");
|
||||||
const QString KEY_FILELOGGER_BACKUP = FILELOGGER_SETTINGS_KEY("Backup");
|
const QString KEY_FILELOGGER_BACKUP = FILELOGGER_SETTINGS_KEY("Backup");
|
||||||
@@ -151,11 +140,11 @@ Application::Application(const QString &id, int &argc, char **argv)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
|
#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
|
#endif
|
||||||
|
|
||||||
connect(this, &Application::messageReceived, this, &Application::processMessage);
|
connect(this, SIGNAL(messageReceived(const QString &)), SLOT(processMessage(const QString &)));
|
||||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
|
connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
|
||||||
|
|
||||||
if (isFileLoggerEnabled())
|
if (isFileLoggerEnabled())
|
||||||
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
|
||||||
@@ -280,58 +269,26 @@ void Application::processMessage(const QString &message)
|
|||||||
m_paramsQueue.append(params);
|
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("%N", torrent->name());
|
||||||
program.replace("%L", torrent->category());
|
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("%F", Utils::Fs::toNativePath(torrent->contentPath()));
|
||||||
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
||||||
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
||||||
#endif
|
|
||||||
program.replace("%C", QString::number(torrent->filesCount()));
|
program.replace("%C", QString::number(torrent->filesCount()));
|
||||||
program.replace("%Z", QString::number(torrent->totalSize()));
|
program.replace("%Z", QString::number(torrent->totalSize()));
|
||||||
program.replace("%T", torrent->currentTracker());
|
program.replace("%T", torrent->currentTracker());
|
||||||
program.replace("%I", torrent->hash());
|
program.replace("%I", torrent->hash());
|
||||||
|
|
||||||
Logger *logger = Logger::instance();
|
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)
|
#if defined(Q_OS_UNIX)
|
||||||
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
|
|
||||||
QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program});
|
QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program});
|
||||||
|
#else
|
||||||
|
QProcess::startDetached(program);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +401,7 @@ void Application::processParams(const QStringList ¶ms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (param.startsWith(QLatin1String("@addPaused="))) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +426,7 @@ void Application::processParams(const QStringList ¶ms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (param.startsWith(QLatin1String("@skipDialog="))) {
|
if (param.startsWith(QLatin1String("@skipDialog="))) {
|
||||||
skipTorrentDialog = param.midRef(12).toInt() ? TriStateBool::True : TriStateBool::False;
|
skipTorrentDialog = param.mid(12).toInt() ? TriStateBool::True : TriStateBool::False;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,8 +458,8 @@ int Application::exec(const QStringList ¶ms)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
BitTorrent::Session::initInstance();
|
BitTorrent::Session::initInstance();
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle *const)), SLOT(torrentFinished(BitTorrent::TorrentHandle *const)));
|
||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
connect(BitTorrent::Session::instance(), SIGNAL(allTorrentsFinished()), SLOT(allTorrentsFinished()), Qt::QueuedConnection);
|
||||||
|
|
||||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||||
Net::GeoIPManager::initInstance();
|
Net::GeoIPManager::initInstance();
|
||||||
@@ -525,16 +482,13 @@ int Application::exec(const QStringList ¶ms)
|
|||||||
#ifndef DISABLE_WEBUI
|
#ifndef DISABLE_WEBUI
|
||||||
Preferences* const pref = Preferences::instance();
|
Preferences* const pref = Preferences::instance();
|
||||||
// Display some information to the user
|
// Display some information to the user
|
||||||
const QString mesg = QString("\n******** %1 ********\n").arg(tr("Information"))
|
std::cout << std::endl << "******** " << qPrintable(tr("Information")) << " ********" << std::endl;
|
||||||
+ tr("To control qBittorrent, access the Web UI at %1")
|
std::cout << qPrintable(tr("To control qBittorrent, access the Web UI at http://localhost:%1").arg(QString::number(pref->getWebUiPort()))) << std::endl;
|
||||||
.arg(QString("http://localhost:") + QString::number(pref->getWebUiPort())) + '\n'
|
std::cout << qPrintable(tr("The Web UI administrator user name is: %1").arg(pref->getWebUiUsername())) << std::endl;
|
||||||
+ tr("The Web UI administrator user name is: %1").arg(pref->getWebUiUsername()) + '\n';
|
|
||||||
printf("%s", qUtf8Printable(mesg));
|
|
||||||
qDebug() << "Password:" << pref->getWebUiPassword();
|
qDebug() << "Password:" << pref->getWebUiPassword();
|
||||||
if (pref->getWebUiPassword() == "f6fdffe48c908deb0f4c3bd36c032e72") {
|
if (pref->getWebUiPassword() == "f6fdffe48c908deb0f4c3bd36c032e72") {
|
||||||
const QString warning = tr("The Web UI administrator password is still the default one: %1").arg("adminadmin") + '\n'
|
std::cout << qPrintable(tr("The Web UI administrator password is still the default one: %1").arg("adminadmin")) << std::endl;
|
||||||
+ tr("This is a security risk, please consider changing your password from program preferences.") + '\n';
|
std::cout << qPrintable(tr("This is a security risk, please consider changing your password from program preferences.")) << std::endl;
|
||||||
printf("%s", qUtf8Printable(warning));
|
|
||||||
}
|
}
|
||||||
#endif // DISABLE_WEBUI
|
#endif // DISABLE_WEBUI
|
||||||
#else
|
#else
|
||||||
@@ -672,7 +626,7 @@ void Application::shutdownCleanup(QSessionManager &manager)
|
|||||||
// According to the qt docs we shouldn't call quit() inside a slot.
|
// According to the qt docs we shouldn't call quit() inside a slot.
|
||||||
// aboutToQuit() is never emitted if the user hits "Cancel" in
|
// aboutToQuit() is never emitted if the user hits "Cancel" in
|
||||||
// the above dialog.
|
// the above dialog.
|
||||||
QTimer::singleShot(0, qApp, &QCoreApplication::quit);
|
QTimer::singleShot(0, qApp, SLOT(quit()));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -693,7 +647,7 @@ void Application::cleanup()
|
|||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
typedef BOOL (WINAPI *PSHUTDOWNBRCREATE)(HWND, LPCWSTR);
|
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+
|
// Only available on Vista+
|
||||||
if (shutdownBRCreate)
|
if (shutdownBRCreate)
|
||||||
shutdownBRCreate((HWND)m_window->effectiveWinId(), tr("Saving torrent progress...").toStdWString().c_str());
|
shutdownBRCreate((HWND)m_window->effectiveWinId(), tr("Saving torrent progress...").toStdWString().c_str());
|
||||||
@@ -735,7 +689,7 @@ void Application::cleanup()
|
|||||||
if (m_window) {
|
if (m_window) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
typedef BOOL (WINAPI *PSHUTDOWNBRDESTROY)(HWND);
|
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+
|
// Only available on Vista+
|
||||||
if (shutdownBRDestroy)
|
if (shutdownBRDestroy)
|
||||||
shutdownBRDestroy((HWND)m_window->effectiveWinId());
|
shutdownBRDestroy((HWND)m_window->effectiveWinId());
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
bool event(QEvent *) override;
|
bool event(QEvent *);
|
||||||
#endif
|
#endif
|
||||||
bool notify(QObject* receiver, QEvent* event) override;
|
bool notify(QObject* receiver, QEvent* event);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@@ -146,7 +146,7 @@ private:
|
|||||||
|
|
||||||
void initializeTranslation();
|
void initializeTranslation();
|
||||||
void processParams(const QStringList ¶ms);
|
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 sendNotificationEmail(const BitTorrent::TorrentHandle *torrent);
|
||||||
void validateCommandLineParameters();
|
void validateCommandLineParameters();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
#include "cmdoptions.h"
|
#include "cmdoptions.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -216,7 +216,7 @@ namespace
|
|||||||
int res = val.toInt(&ok);
|
int res = val.toInt(&ok);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
|
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
|
||||||
.arg(envVarName(), val);
|
.arg(envVarName()).arg(val);
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -293,7 +293,7 @@ namespace
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
|
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;
|
return TriStateBool::Undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,7 +578,7 @@ QString makeUsage(const QString &prgName)
|
|||||||
void displayUsage(const QString &prgName)
|
void displayUsage(const QString &prgName)
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
printf("%s\n", qUtf8Printable(makeUsage(prgName)));
|
std::cout << qPrintable(makeUsage(prgName)) << std::endl;
|
||||||
#else
|
#else
|
||||||
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
|
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
|
||||||
msgBox.show(); // Need to be shown or to moveToCenter does not work
|
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.setInterval(0);
|
||||||
m_flusher.setSingleShot(true);
|
m_flusher.setSingleShot(true);
|
||||||
connect(&m_flusher, &QTimer::timeout, this, &FileLogger::flushLog);
|
connect(&m_flusher, SIGNAL(timeout()), SLOT(flushLog()));
|
||||||
|
|
||||||
changePath(path);
|
changePath(path);
|
||||||
if (deleteOld)
|
if (deleteOld)
|
||||||
@@ -51,7 +51,7 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
|
|||||||
foreach (const Log::Msg& msg, logger->getMessages())
|
foreach (const Log::Msg& msg, logger->getMessages())
|
||||||
addLogMessage(msg);
|
addLogMessage(msg);
|
||||||
|
|
||||||
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
|
connect(logger, SIGNAL(newLogMessage(const Log::Msg &)), SLOT(addLogMessage(const Log::Msg &)));
|
||||||
}
|
}
|
||||||
|
|
||||||
FileLogger::~FileLogger()
|
FileLogger::~FileLogger()
|
||||||
@@ -83,21 +83,21 @@ void FileLogger::changePath(const QString& newPath)
|
|||||||
void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
||||||
{
|
{
|
||||||
QDateTime date = QDateTime::currentDateTime();
|
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)) {
|
foreach (const QFileInfo file, dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed)) {
|
||||||
QDateTime modificationDate = file.lastModified();
|
if (file.lastModified() < date)
|
||||||
switch (ageType) {
|
|
||||||
case DAYS:
|
|
||||||
modificationDate = modificationDate.addDays(age);
|
|
||||||
break;
|
|
||||||
case MONTHS:
|
|
||||||
modificationDate = modificationDate.addMonths(age);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
modificationDate = modificationDate.addYears(age);
|
|
||||||
}
|
|
||||||
if (modificationDate > date)
|
|
||||||
break;
|
break;
|
||||||
Utils::Fs::forceRemove(file.absoluteFilePath());
|
Utils::Fs::forceRemove(file.absoluteFilePath());
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ void FileLogger::openLogFile()
|
|||||||
|| !m_logFile->setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
|
|| !m_logFile->setPermissions(QFile::ReadOwner | QFile::WriteOwner)) {
|
||||||
delete m_logFile;
|
delete m_logFile;
|
||||||
m_logFile = nullptr;
|
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
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
@@ -57,28 +55,33 @@ Q_IMPORT_PLUGIN(QICOPlugin)
|
|||||||
#endif
|
#endif
|
||||||
#endif // DISABLE_GUI
|
#endif // DISABLE_GUI
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
#ifdef STACKTRACE
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
|
#include <signal.h>
|
||||||
|
#include <execinfo.h>
|
||||||
#include "stacktrace.h"
|
#include "stacktrace.h"
|
||||||
#else
|
#endif // Q_OS_UNIX
|
||||||
|
|
||||||
|
#ifdef STACKTRACE_WIN
|
||||||
|
#include <signal.h>
|
||||||
#include "stacktrace_win.h"
|
#include "stacktrace_win.h"
|
||||||
#include "stacktrace_win_dlg.h"
|
#include "stacktrace_win_dlg.h"
|
||||||
#endif // Q_OS_UNIX
|
#endif //STACKTRACE_WIN
|
||||||
#endif //STACKTRACE
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "base/profile.h"
|
#include "base/profile.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "cmdoptions.h"
|
#include "cmdoptions.h"
|
||||||
|
|
||||||
#include "upgrade.h"
|
#include "upgrade.h"
|
||||||
|
|
||||||
// Signal handlers
|
// Signal handlers
|
||||||
|
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||||
void sigNormalHandler(int signum);
|
void sigNormalHandler(int signum);
|
||||||
#ifdef STACKTRACE
|
|
||||||
void sigAbnormalHandler(int signum);
|
void sigAbnormalHandler(int signum);
|
||||||
#endif
|
|
||||||
// sys_signame[] is only defined in BSD
|
// sys_signame[] is only defined in BSD
|
||||||
const char *sysSigName[] = {
|
const char *sysSigName[] = {
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
@@ -93,6 +96,7 @@ const char *sysSigName[] = {
|
|||||||
"SIGPWR", "SIGUNUSED"
|
"SIGPWR", "SIGUNUSED"
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||||
void reportToUser(const char* str);
|
void reportToUser(const char* str);
|
||||||
@@ -165,7 +169,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// Set environment variable
|
// Set environment variable
|
||||||
if (!qputenv("QBITTORRENT", QBT_VERSION))
|
if (!qputenv("QBITTORRENT", QBT_VERSION))
|
||||||
fprintf(stderr, "Couldn't set environment variable...\n");
|
std::cerr << "Couldn't set environment variable...\n";
|
||||||
|
|
||||||
#ifndef DISABLE_GUI
|
#ifndef DISABLE_GUI
|
||||||
if (!userAgreesWithLegalNotice())
|
if (!userAgreesWithLegalNotice())
|
||||||
@@ -253,9 +257,9 @@ int main(int argc, char *argv[])
|
|||||||
showSplashScreen();
|
showSplashScreen();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||||
signal(SIGINT, sigNormalHandler);
|
signal(SIGINT, sigNormalHandler);
|
||||||
signal(SIGTERM, sigNormalHandler);
|
signal(SIGTERM, sigNormalHandler);
|
||||||
#ifdef STACKTRACE
|
|
||||||
signal(SIGABRT, sigAbnormalHandler);
|
signal(SIGABRT, sigAbnormalHandler);
|
||||||
signal(SIGSEGV, sigAbnormalHandler);
|
signal(SIGSEGV, sigAbnormalHandler);
|
||||||
#endif
|
#endif
|
||||||
@@ -279,6 +283,7 @@ void reportToUser(const char* str)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||||
void sigNormalHandler(int signum)
|
void sigNormalHandler(int signum)
|
||||||
{
|
{
|
||||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||||
@@ -292,7 +297,6 @@ void sigNormalHandler(int signum)
|
|||||||
qApp->exit(); // unsafe, but exit anyway
|
qApp->exit(); // unsafe, but exit anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef STACKTRACE
|
|
||||||
void sigAbnormalHandler(int signum)
|
void sigAbnormalHandler(int signum)
|
||||||
{
|
{
|
||||||
const char *sigName = sysSigName[signum];
|
const char *sigName = sysSigName[signum];
|
||||||
@@ -305,18 +309,16 @@ void sigAbnormalHandler(int signum)
|
|||||||
reportToUser(sigName);
|
reportToUser(sigName);
|
||||||
reportToUser("\n");
|
reportToUser("\n");
|
||||||
print_stacktrace(); // unsafe
|
print_stacktrace(); // unsafe
|
||||||
#endif
|
#endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||||
|
#ifdef STACKTRACE_WIN
|
||||||
#if defined Q_OS_WIN
|
|
||||||
StraceDlg dlg; // unsafe
|
StraceDlg dlg; // unsafe
|
||||||
dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
|
dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
#endif
|
#endif // STACKTRACE_WIN
|
||||||
|
|
||||||
signal(signum, SIG_DFL);
|
signal(signum, SIG_DFL);
|
||||||
raise(signum);
|
raise(signum);
|
||||||
}
|
}
|
||||||
#endif // STACKTRACE
|
#endif // defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
|
||||||
|
|
||||||
#if !defined(DISABLE_GUI)
|
#if !defined(DISABLE_GUI)
|
||||||
void showSplashScreen()
|
void showSplashScreen()
|
||||||
@@ -329,14 +331,14 @@ void showSplashScreen()
|
|||||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
||||||
QSplashScreen *splash = new QSplashScreen(splash_img);
|
QSplashScreen *splash = new QSplashScreen(splash_img);
|
||||||
splash->show();
|
splash->show();
|
||||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
QTimer::singleShot(1500, splash, SLOT(deleteLater()));
|
||||||
qApp->processEvents();
|
qApp->processEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
void setupDpi()
|
void setupDpi()
|
||||||
{
|
{
|
||||||
if (qEnvironmentVariableIsEmpty("QT_AUTO_SCREEN_SCALE_FACTOR"))
|
if (qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR").isEmpty())
|
||||||
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
|
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
|
||||||
}
|
}
|
||||||
#endif // Q_OS_UNIX
|
#endif // Q_OS_UNIX
|
||||||
@@ -344,7 +346,7 @@ void setupDpi()
|
|||||||
|
|
||||||
void displayVersion()
|
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)
|
void displayBadArgMessage(const QString& message)
|
||||||
@@ -357,10 +359,9 @@ void displayBadArgMessage(const QString& message)
|
|||||||
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
#else
|
#else
|
||||||
const QString errMsg = QObject::tr("Bad command line: ") + '\n'
|
std::cerr << qPrintable(QObject::tr("Bad command line: "));
|
||||||
+ message + '\n'
|
std::cerr << qPrintable(message) << std::endl;
|
||||||
+ help + '\n';
|
std::cerr << qPrintable(help) << std::endl;
|
||||||
fprintf(stderr, "%s", qUtf8Printable(errMsg));
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,12 +372,9 @@ bool userAgreesWithLegalNotice()
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
#ifdef DISABLE_GUI
|
#ifdef DISABLE_GUI
|
||||||
const QString eula = QString("\n*** %1 ***\n").arg(QObject::tr("Legal Notice"))
|
std::cout << std::endl << "*** " << qPrintable(QObject::tr("Legal Notice")) << " ***" << std::endl;
|
||||||
+ 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"
|
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;
|
||||||
+ QObject::tr("No further notices will be issued.") + "\n\n"
|
std::cout << qPrintable(QObject::tr("Press %1 key to accept and continue...").arg("'y'")) << std::endl;
|
||||||
+ QObject::tr("Press %1 key to accept and continue...").arg("'y'") + '\n';
|
|
||||||
printf("%s", qUtf8Printable(eula));
|
|
||||||
|
|
||||||
char ret = getchar(); // Read pressed key
|
char ret = getchar(); // Read pressed key
|
||||||
if (ret == 'y' || ret == 'Y') {
|
if (ret == 'y' || ret == 'Y') {
|
||||||
// Save the answer
|
// Save the answer
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ bool straceWin::makeRelativePath(const QString& dir, QString& file)
|
|||||||
|
|
||||||
QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
QString straceWin::getSourcePathAndLineNumber(HANDLE hProcess, DWORD64 addr)
|
||||||
{
|
{
|
||||||
IMAGEHLP_LINE64 line {};
|
IMAGEHLP_LINE64 line = {0};
|
||||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||||
DWORD dwDisplacement = 0;
|
DWORD dwDisplacement = 0;
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ const QString straceWin::getBacktrace()
|
|||||||
demangle(funcName);
|
demangle(funcName);
|
||||||
#endif
|
#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
|
// decrease the query address by one byte to point somewhere in the CALL instruction byte sequence
|
||||||
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
|
sourceFile = getSourcePathAndLineNumber(hProcess, ihsf.InstructionOffset - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,10 @@
|
|||||||
bool userAcceptsUpgrade()
|
bool userAcceptsUpgrade()
|
||||||
{
|
{
|
||||||
#ifdef DISABLE_GUI
|
#ifdef DISABLE_GUI
|
||||||
printf("\n*** %s ***\n", qUtf8Printable(QObject::tr("Upgrade")));
|
std::cout << std::endl << "*** " << qPrintable(QObject::tr("Upgrade")) << " ***" << std::endl;
|
||||||
char ret = '\0';
|
char ret = '\0';
|
||||||
do {
|
do {
|
||||||
printf("%s\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;
|
||||||
, 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]")));
|
|
||||||
ret = getchar(); // Read pressed key
|
ret = getchar(); // Read pressed key
|
||||||
}
|
}
|
||||||
while ((ret != 'y') && (ret != 'Y') && (ret != 'n') && (ret != 'N'));
|
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;
|
bool v3_3 = false;
|
||||||
int queuePosition = 0;
|
int queuePosition = 0;
|
||||||
QString outFilePath = filepath;
|
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) {
|
if (rx.indexIn(filepath) != -1) {
|
||||||
// Old v3.3.x format had a number at the end indicating the queue position.
|
// old v3.3.x format
|
||||||
// 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
|
|
||||||
queuePosition = rx.cap(2).toInt();
|
queuePosition = rx.cap(2).toInt();
|
||||||
if ((rx.cap(2).size() == 6) && (queuePosition <= 99999)) {
|
|
||||||
Utils::Fs::forceRemove(filepath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
v3_3 = true;
|
v3_3 = true;
|
||||||
outFilePath.replace(QRegExp("\\.fastresume\\..+$"), ".fastresume");
|
outFilePath.replace(QRegExp("\\.\\d+$"), "");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
queuePosition = fastOld.dict_find_int_value("qBt-queuePosition", 0);
|
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
|
// in versions < 3.3 we have -1 for seeding torrents, so we convert it to 0
|
||||||
fastNew["qBt-queuePosition"] = (queuePosition >= 0 ? queuePosition : 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);
|
QFile file2(outFilePath);
|
||||||
QVector<char> out;
|
QVector<char> out;
|
||||||
libtorrent::bencode(std::back_inserter(out), fastNew);
|
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) {
|
foreach (const QString &hash, oldResumeData.keys()) {
|
||||||
const QVariantHash oldTorrent = i.value().toHash();
|
QVariantHash oldTorrent = oldResumeData[hash].toHash();
|
||||||
if (oldTorrent.value("is_magnet", false).toBool()) {
|
if (oldTorrent.value("is_magnet", false).toBool()) {
|
||||||
const QString &hash = i.key();
|
|
||||||
libtorrent::entry resumeData;
|
libtorrent::entry resumeData;
|
||||||
resumeData["qBt-magnetUri"] = oldTorrent.value("magnet_uri").toString().toStdString();
|
resumeData["qBt-magnetUri"] = oldTorrent.value("magnet_uri").toString().toStdString();
|
||||||
resumeData["qBt-paused"] = false;
|
resumeData["qBt-paused"] = false;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ bittorrent/torrentinfo.h
|
|||||||
bittorrent/tracker.h
|
bittorrent/tracker.h
|
||||||
bittorrent/trackerentry.h
|
bittorrent/trackerentry.h
|
||||||
http/connection.h
|
http/connection.h
|
||||||
http/httperror.h
|
|
||||||
http/irequesthandler.h
|
http/irequesthandler.h
|
||||||
http/requestparser.h
|
http/requestparser.h
|
||||||
http/responsebuilder.h
|
http/responsebuilder.h
|
||||||
@@ -44,10 +43,6 @@ rss/rss_feed.h
|
|||||||
rss/rss_folder.h
|
rss/rss_folder.h
|
||||||
rss/rss_item.h
|
rss/rss_item.h
|
||||||
rss/rss_session.h
|
rss/rss_session.h
|
||||||
search/searchdownloadhandler.h
|
|
||||||
search/searchhandler.h
|
|
||||||
search/searchpluginmanager.h
|
|
||||||
utils/bytearray.h
|
|
||||||
utils/fs.h
|
utils/fs.h
|
||||||
utils/gzip.h
|
utils/gzip.h
|
||||||
utils/misc.h
|
utils/misc.h
|
||||||
@@ -55,9 +50,7 @@ utils/net.h
|
|||||||
utils/random.h
|
utils/random.h
|
||||||
utils/string.h
|
utils/string.h
|
||||||
utils/version.h
|
utils/version.h
|
||||||
algorithm.h
|
|
||||||
asyncfilestorage.h
|
asyncfilestorage.h
|
||||||
exceptions.h
|
|
||||||
filesystemwatcher.h
|
filesystemwatcher.h
|
||||||
global.h
|
global.h
|
||||||
iconprovider.h
|
iconprovider.h
|
||||||
@@ -66,6 +59,7 @@ logger.h
|
|||||||
preferences.h
|
preferences.h
|
||||||
profile.h
|
profile.h
|
||||||
scanfoldersmodel.h
|
scanfoldersmodel.h
|
||||||
|
searchengine.h
|
||||||
settingsstorage.h
|
settingsstorage.h
|
||||||
torrentfileguard.h
|
torrentfileguard.h
|
||||||
torrentfilter.h
|
torrentfilter.h
|
||||||
@@ -90,7 +84,6 @@ bittorrent/torrentinfo.cpp
|
|||||||
bittorrent/tracker.cpp
|
bittorrent/tracker.cpp
|
||||||
bittorrent/trackerentry.cpp
|
bittorrent/trackerentry.cpp
|
||||||
http/connection.cpp
|
http/connection.cpp
|
||||||
http/httperror.cpp
|
|
||||||
http/requestparser.cpp
|
http/requestparser.cpp
|
||||||
http/responsebuilder.cpp
|
http/responsebuilder.cpp
|
||||||
http/responsegenerator.cpp
|
http/responsegenerator.cpp
|
||||||
@@ -113,10 +106,6 @@ rss/rss_feed.cpp
|
|||||||
rss/rss_folder.cpp
|
rss/rss_folder.cpp
|
||||||
rss/rss_item.cpp
|
rss/rss_item.cpp
|
||||||
rss/rss_session.cpp
|
rss/rss_session.cpp
|
||||||
search/searchdownloadhandler.cpp
|
|
||||||
search/searchhandler.cpp
|
|
||||||
search/searchpluginmanager.cpp
|
|
||||||
utils/bytearray.cpp
|
|
||||||
utils/fs.cpp
|
utils/fs.cpp
|
||||||
utils/gzip.cpp
|
utils/gzip.cpp
|
||||||
utils/misc.cpp
|
utils/misc.cpp
|
||||||
@@ -124,13 +113,13 @@ utils/net.cpp
|
|||||||
utils/random.cpp
|
utils/random.cpp
|
||||||
utils/string.cpp
|
utils/string.cpp
|
||||||
asyncfilestorage.cpp
|
asyncfilestorage.cpp
|
||||||
exceptions.cpp
|
|
||||||
filesystemwatcher.cpp
|
filesystemwatcher.cpp
|
||||||
iconprovider.cpp
|
iconprovider.cpp
|
||||||
logger.cpp
|
logger.cpp
|
||||||
preferences.cpp
|
preferences.cpp
|
||||||
profile.cpp
|
profile.cpp
|
||||||
scanfoldersmodel.cpp
|
scanfoldersmodel.cpp
|
||||||
|
searchengine.cpp
|
||||||
settingsstorage.cpp
|
settingsstorage.cpp
|
||||||
torrentfileguard.cpp
|
torrentfileguard.cpp
|
||||||
torrentfilter.cpp
|
torrentfilter.cpp
|
||||||
|
|||||||
@@ -34,14 +34,14 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class AsyncFileStorageError : public std::runtime_error
|
class AsyncFileStorageError: public std::runtime_error
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit AsyncFileStorageError(const QString &message);
|
explicit AsyncFileStorageError(const QString &message);
|
||||||
QString message() const;
|
QString message() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncFileStorage : public QObject
|
class AsyncFileStorage: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/algorithm.h \
|
|
||||||
$$PWD/asyncfilestorage.h \
|
$$PWD/asyncfilestorage.h \
|
||||||
$$PWD/bittorrent/addtorrentparams.h \
|
$$PWD/bittorrent/addtorrentparams.h \
|
||||||
$$PWD/bittorrent/cachestatus.h \
|
$$PWD/bittorrent/cachestatus.h \
|
||||||
@@ -18,11 +17,9 @@ HEADERS += \
|
|||||||
$$PWD/bittorrent/torrentinfo.h \
|
$$PWD/bittorrent/torrentinfo.h \
|
||||||
$$PWD/bittorrent/tracker.h \
|
$$PWD/bittorrent/tracker.h \
|
||||||
$$PWD/bittorrent/trackerentry.h \
|
$$PWD/bittorrent/trackerentry.h \
|
||||||
$$PWD/exceptions.h \
|
|
||||||
$$PWD/filesystemwatcher.h \
|
$$PWD/filesystemwatcher.h \
|
||||||
$$PWD/global.h \
|
$$PWD/global.h \
|
||||||
$$PWD/http/connection.h \
|
$$PWD/http/connection.h \
|
||||||
$$PWD/http/httperror.h \
|
|
||||||
$$PWD/http/irequesthandler.h \
|
$$PWD/http/irequesthandler.h \
|
||||||
$$PWD/http/requestparser.h \
|
$$PWD/http/requestparser.h \
|
||||||
$$PWD/http/responsebuilder.h \
|
$$PWD/http/responsebuilder.h \
|
||||||
@@ -53,9 +50,7 @@ HEADERS += \
|
|||||||
$$PWD/rss/rss_item.h \
|
$$PWD/rss/rss_item.h \
|
||||||
$$PWD/rss/rss_session.h \
|
$$PWD/rss/rss_session.h \
|
||||||
$$PWD/scanfoldersmodel.h \
|
$$PWD/scanfoldersmodel.h \
|
||||||
$$PWD/search/searchhandler.h \
|
$$PWD/searchengine.h \
|
||||||
$$PWD/search/searchdownloadhandler.h \
|
|
||||||
$$PWD/search/searchpluginmanager.h \
|
|
||||||
$$PWD/settingsstorage.h \
|
$$PWD/settingsstorage.h \
|
||||||
$$PWD/settingvalue.h \
|
$$PWD/settingvalue.h \
|
||||||
$$PWD/torrentfileguard.h \
|
$$PWD/torrentfileguard.h \
|
||||||
@@ -63,7 +58,6 @@ HEADERS += \
|
|||||||
$$PWD/tristatebool.h \
|
$$PWD/tristatebool.h \
|
||||||
$$PWD/types.h \
|
$$PWD/types.h \
|
||||||
$$PWD/unicodestrings.h \
|
$$PWD/unicodestrings.h \
|
||||||
$$PWD/utils/bytearray.h \
|
|
||||||
$$PWD/utils/fs.h \
|
$$PWD/utils/fs.h \
|
||||||
$$PWD/utils/gzip.h \
|
$$PWD/utils/gzip.h \
|
||||||
$$PWD/utils/misc.h \
|
$$PWD/utils/misc.h \
|
||||||
@@ -88,10 +82,8 @@ SOURCES += \
|
|||||||
$$PWD/bittorrent/torrentinfo.cpp \
|
$$PWD/bittorrent/torrentinfo.cpp \
|
||||||
$$PWD/bittorrent/tracker.cpp \
|
$$PWD/bittorrent/tracker.cpp \
|
||||||
$$PWD/bittorrent/trackerentry.cpp \
|
$$PWD/bittorrent/trackerentry.cpp \
|
||||||
$$PWD/exceptions.cpp \
|
|
||||||
$$PWD/filesystemwatcher.cpp \
|
$$PWD/filesystemwatcher.cpp \
|
||||||
$$PWD/http/connection.cpp \
|
$$PWD/http/connection.cpp \
|
||||||
$$PWD/http/httperror.cpp \
|
|
||||||
$$PWD/http/requestparser.cpp \
|
$$PWD/http/requestparser.cpp \
|
||||||
$$PWD/http/responsebuilder.cpp \
|
$$PWD/http/responsebuilder.cpp \
|
||||||
$$PWD/http/responsegenerator.cpp \
|
$$PWD/http/responsegenerator.cpp \
|
||||||
@@ -119,14 +111,11 @@ SOURCES += \
|
|||||||
$$PWD/rss/rss_item.cpp \
|
$$PWD/rss/rss_item.cpp \
|
||||||
$$PWD/rss/rss_session.cpp \
|
$$PWD/rss/rss_session.cpp \
|
||||||
$$PWD/scanfoldersmodel.cpp \
|
$$PWD/scanfoldersmodel.cpp \
|
||||||
$$PWD/search/searchdownloadhandler.cpp \
|
$$PWD/searchengine.cpp \
|
||||||
$$PWD/search/searchhandler.cpp \
|
|
||||||
$$PWD/search/searchpluginmanager.cpp \
|
|
||||||
$$PWD/settingsstorage.cpp \
|
$$PWD/settingsstorage.cpp \
|
||||||
$$PWD/torrentfileguard.cpp \
|
$$PWD/torrentfileguard.cpp \
|
||||||
$$PWD/torrentfilter.cpp \
|
$$PWD/torrentfilter.cpp \
|
||||||
$$PWD/tristatebool.cpp \
|
$$PWD/tristatebool.cpp \
|
||||||
$$PWD/utils/bytearray.cpp \
|
|
||||||
$$PWD/utils/fs.cpp \
|
$$PWD/utils/fs.cpp \
|
||||||
$$PWD/utils/gzip.cpp \
|
$$PWD/utils/gzip.cpp \
|
||||||
$$PWD/utils/misc.cpp \
|
$$PWD/utils/misc.cpp \
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ bool InfoHash::operator!=(const InfoHash &other) const
|
|||||||
return (m_nativeHash != other.m_nativeHash);
|
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);
|
return qHash(static_cast<QString>(key), seed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ namespace BitTorrent
|
|||||||
libtorrent::sha1_hash m_nativeHash;
|
libtorrent::sha1_hash m_nativeHash;
|
||||||
QString m_hashString;
|
QString m_hashString;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint qHash(const InfoHash &key, uint seed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint qHash(const BitTorrent::InfoHash &key, uint seed);
|
||||||
|
|
||||||
#endif // BITTORRENT_INFOHASH_H
|
#endif // BITTORRENT_INFOHASH_H
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ void BandwidthScheduler::start()
|
|||||||
|
|
||||||
bool BandwidthScheduler::isTimeForAlternative() const
|
bool BandwidthScheduler::isTimeForAlternative() const
|
||||||
{
|
{
|
||||||
const Preferences *const pref = Preferences::instance();
|
const Preferences* const pref = Preferences::instance();
|
||||||
|
|
||||||
QTime start = pref->getSchedulerStartTime();
|
QTime start = pref->getSchedulerStartTime();
|
||||||
QTime end = pref->getSchedulerEndTime();
|
QTime end = pref->getSchedulerEndTime();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
class BandwidthScheduler : public QObject
|
class BandwidthScheduler: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(BandwidthScheduler)
|
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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -147,13 +147,13 @@ int FilterParserThread::parseDATFilterFile()
|
|||||||
if (bytesRead < 0)
|
if (bytesRead < 0)
|
||||||
break;
|
break;
|
||||||
int dataSize = bytesRead + offset;
|
int dataSize = bytesRead + offset;
|
||||||
if ((bytesRead == 0) && (dataSize == 0))
|
if (bytesRead == 0 && dataSize == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
for (start = 0; start < dataSize; ++start) {
|
for (start = 0; start < dataSize; ++start) {
|
||||||
endOfLine = -1;
|
endOfLine = -1;
|
||||||
// The file might have ended without the last line having a newline
|
// The file might have ended without the last line having a newline
|
||||||
if (!((bytesRead == 0) && (dataSize > 0))) {
|
if (!(bytesRead == 0 && dataSize > 0)) {
|
||||||
for (int i = start; i < dataSize; ++i) {
|
for (int i = start; i < dataSize; ++i) {
|
||||||
if (buffer[i] == '\n') {
|
if (buffer[i] == '\n') {
|
||||||
endOfLine = i;
|
endOfLine = i;
|
||||||
@@ -295,13 +295,13 @@ int FilterParserThread::parseP2PFilterFile()
|
|||||||
if (bytesRead < 0)
|
if (bytesRead < 0)
|
||||||
break;
|
break;
|
||||||
int dataSize = bytesRead + offset;
|
int dataSize = bytesRead + offset;
|
||||||
if ((bytesRead == 0) && (dataSize == 0))
|
if (bytesRead == 0 && dataSize == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
for (start = 0; start < dataSize; ++start) {
|
for (start = 0; start < dataSize; ++start) {
|
||||||
endOfLine = -1;
|
endOfLine = -1;
|
||||||
// The file might have ended without the last line having a newline
|
// The file might have ended without the last line having a newline
|
||||||
if (!((bytesRead == 0) && (dataSize > 0))) {
|
if (!(bytesRead == 0 && dataSize > 0)) {
|
||||||
for (int i = start; i < dataSize; ++i) {
|
for (int i = start; i < dataSize; ++i) {
|
||||||
if (buffer[i] == '\n') {
|
if (buffer[i] == '\n') {
|
||||||
endOfLine = i;
|
endOfLine = i;
|
||||||
@@ -610,7 +610,7 @@ int FilterParserThread::findAndNullDelimiter(char *const data, char delimiter, i
|
|||||||
return -1;
|
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;
|
if (start >= end) return start;
|
||||||
int newStart = start;
|
int newStart = start;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ void ResumeDataSavingManager::saveResumeData(QString infoHash, QByteArray data)
|
|||||||
resumeFile.write(data);
|
resumeFile.write(data);
|
||||||
if (!resumeFile.commit()) {
|
if (!resumeFile.commit()) {
|
||||||
Logger::instance()->addMessage(QString("Couldn't save resume data in %1. Error: %2")
|
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)
|
, m_dirty(false)
|
||||||
{
|
{
|
||||||
load();
|
load();
|
||||||
connect(&m_timer, &QTimer::timeout, this, &Statistics::gather);
|
connect(&m_timer, SIGNAL(timeout()), this, SLOT(gather()));
|
||||||
m_timer.start(60 * 1000);
|
m_timer.start(60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace BitTorrent
|
|||||||
class Session;
|
class Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Statistics : public QObject
|
class Statistics : QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Statistics)
|
Q_DISABLE_COPY(Statistics)
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
|
|
||||||
#include "session.h"
|
#include "session.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
@@ -49,6 +49,9 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include <libtorrent/alert_types.hpp>
|
#include <libtorrent/alert_types.hpp>
|
||||||
|
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||||
|
#include <libtorrent/bdecode.hpp>
|
||||||
|
#endif
|
||||||
#include <libtorrent/bencode.hpp>
|
#include <libtorrent/bencode.hpp>
|
||||||
#include <libtorrent/disk_io_thread.hpp>
|
#include <libtorrent/disk_io_thread.hpp>
|
||||||
#include <libtorrent/error_code.hpp>
|
#include <libtorrent/error_code.hpp>
|
||||||
@@ -57,19 +60,17 @@
|
|||||||
#include <libtorrent/extensions/smart_ban.hpp>
|
#include <libtorrent/extensions/smart_ban.hpp>
|
||||||
#include <libtorrent/identify_client.hpp>
|
#include <libtorrent/identify_client.hpp>
|
||||||
#include <libtorrent/ip_filter.hpp>
|
#include <libtorrent/ip_filter.hpp>
|
||||||
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
|
#include <libtorrent/lazy_entry.hpp>
|
||||||
|
#endif
|
||||||
#include <libtorrent/magnet_uri.hpp>
|
#include <libtorrent/magnet_uri.hpp>
|
||||||
#include <libtorrent/session.hpp>
|
#include <libtorrent/session.hpp>
|
||||||
|
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||||
|
#include <libtorrent/session_stats.hpp>
|
||||||
|
#endif
|
||||||
#include <libtorrent/session_status.hpp>
|
#include <libtorrent/session_status.hpp>
|
||||||
#include <libtorrent/torrent_info.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/logger.h"
|
||||||
#include "base/net/downloadhandler.h"
|
#include "base/net/downloadhandler.h"
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
@@ -94,7 +95,6 @@
|
|||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <wincrypt.h>
|
|
||||||
#include <iphlpapi.h>
|
#include <iphlpapi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -123,19 +123,21 @@ namespace
|
|||||||
QString convertIfaceNameToGuid(const QString &name);
|
QString convertIfaceNameToGuid(const QString &name);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||||
|
|
||||||
QStringMap map_cast(const QVariantMap &map)
|
QStringMap map_cast(const QVariantMap &map)
|
||||||
{
|
{
|
||||||
QStringMap result;
|
QStringMap result;
|
||||||
for (auto i = map.cbegin(); i != map.cend(); ++i)
|
foreach (const QString &key, map.keys())
|
||||||
result[i.key()] = i.value().toString();
|
result[key] = map.value(key).toString();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap map_cast(const QStringMap &map)
|
QVariantMap map_cast(const QStringMap &map)
|
||||||
{
|
{
|
||||||
QVariantMap result;
|
QVariantMap result;
|
||||||
for (auto i = map.cbegin(); i != map.cend(); ++i)
|
foreach (const QString &key, map.keys())
|
||||||
result[i.key()] = i.value();
|
result[key] = map.value(key);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +199,7 @@ namespace
|
|||||||
{
|
{
|
||||||
QStringMap expanded = categories;
|
QStringMap expanded = categories;
|
||||||
|
|
||||||
for (auto i = categories.cbegin(); i != categories.cend(); ++i) {
|
foreach (const QString &category, categories.keys()) {
|
||||||
const QString &category = i.key();
|
|
||||||
foreach (const QString &subcat, Session::expandCategory(category)) {
|
foreach (const QString &subcat, Session::expandCategory(category)) {
|
||||||
if (!expanded.contains(subcat))
|
if (!expanded.contains(subcat))
|
||||||
expanded[subcat] = "";
|
expanded[subcat] = "";
|
||||||
@@ -279,11 +280,6 @@ Session::Session(QObject *parent)
|
|||||||
, m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
|
, m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
|
||||||
, m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
|
, m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
|
||||||
, m_guidedReadCacheEnabled(BITTORRENT_SESSION_KEY("GuidedReadCache"), 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_isSuggestMode(BITTORRENT_SESSION_KEY("SuggestMode"), false)
|
||||||
, m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
|
, m_sendBufferWatermark(BITTORRENT_SESSION_KEY("SendBufferWatermark"), 500)
|
||||||
, m_sendBufferLowWatermark(BITTORRENT_SESSION_KEY("SendBufferLowWatermark"), 10)
|
, 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_maxActiveUploads(BITTORRENT_SESSION_KEY("MaxActiveUploads"), 3, lowerLimited(-1))
|
||||||
, m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
|
, m_maxActiveTorrents(BITTORRENT_SESSION_KEY("MaxActiveTorrents"), 5, lowerLimited(-1))
|
||||||
, m_ignoreSlowTorrentsForQueueing(BITTORRENT_SESSION_KEY("IgnoreSlowTorrentsForQueueing"), false)
|
, 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_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
||||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
||||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), true)
|
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), true)
|
||||||
@@ -370,16 +363,11 @@ Session::Session(QObject *parent)
|
|||||||
, m_numResumeData(0)
|
, m_numResumeData(0)
|
||||||
, m_extraLimit(0)
|
, m_extraLimit(0)
|
||||||
, m_useProxy(false)
|
, m_useProxy(false)
|
||||||
, m_recentErroredTorrentsTimer(new QTimer(this))
|
|
||||||
{
|
{
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
|
|
||||||
initResumeFolder();
|
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 = new QTimer(this);
|
||||||
m_seedingLimitTimer->setInterval(10000);
|
m_seedingLimitTimer->setInterval(10000);
|
||||||
connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
|
connect(m_seedingLimitTimer, &QTimer::timeout, this, &Session::processShareLimits);
|
||||||
@@ -523,14 +511,13 @@ Session::Session(QObject *parent)
|
|||||||
|
|
||||||
enableTracker(isTrackerEnabled());
|
enableTracker(isTrackerEnabled());
|
||||||
|
|
||||||
connect(Net::ProxyConfigurationManager::instance(), &Net::ProxyConfigurationManager::proxyConfigurationChanged
|
connect(Net::ProxyConfigurationManager::instance(), SIGNAL(proxyConfigurationChanged()), SLOT(configureDeferred()));
|
||||||
, this, &Session::configureDeferred);
|
|
||||||
|
|
||||||
// Network configuration monitor
|
// Network configuration monitor
|
||||||
connect(&m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &Session::networkOnlineStateChanged);
|
connect(&m_networkManager, SIGNAL(onlineStateChanged(bool)), SLOT(networkOnlineStateChanged(bool)));
|
||||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationAdded, this, &Session::networkConfigurationChange);
|
connect(&m_networkManager, SIGNAL(configurationAdded(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationRemoved, this, &Session::networkConfigurationChange);
|
connect(&m_networkManager, SIGNAL(configurationRemoved(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange);
|
connect(&m_networkManager, SIGNAL(configurationChanged(const QNetworkConfiguration&)), SLOT(networkConfigurationChange(const QNetworkConfiguration&)));
|
||||||
|
|
||||||
m_ioThread = new QThread(this);
|
m_ioThread = new QThread(this);
|
||||||
m_resumeDataSavingManager = new ResumeDataSavingManager(m_resumeFolderPath);
|
m_resumeDataSavingManager = new ResumeDataSavingManager(m_resumeFolderPath);
|
||||||
@@ -790,16 +777,14 @@ bool Session::removeCategory(const QString &name)
|
|||||||
bool result = false;
|
bool result = false;
|
||||||
if (isSubcategoriesEnabled()) {
|
if (isSubcategoriesEnabled()) {
|
||||||
// remove subcategories
|
// remove subcategories
|
||||||
const QString test = name + "/";
|
QString test = name + "/";
|
||||||
Dict::removeIf(m_categories, [this, &test, &result](const QString &category, const QString &)
|
foreach (const QString &category, m_categories.keys()) {
|
||||||
{
|
|
||||||
if (category.startsWith(test)) {
|
if (category.startsWith(test)) {
|
||||||
|
m_categories.remove(category);
|
||||||
result = true;
|
result = true;
|
||||||
emit categoryRemoved(category);
|
emit categoryRemoved(category);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = (m_categories.remove(name) > 0) || result;
|
result = (m_categories.remove(name) > 0) || result;
|
||||||
@@ -946,7 +931,7 @@ qreal Session::globalMaxRatio() const
|
|||||||
return m_globalMaxRatio;
|
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
|
// be automatically deleted
|
||||||
void Session::setGlobalMaxRatio(qreal ratio)
|
void Session::setGlobalMaxRatio(qreal ratio)
|
||||||
{
|
{
|
||||||
@@ -1010,7 +995,7 @@ void Session::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete 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
|
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||||
void Session::adjustLimits(libt::settings_pack &settingsPack)
|
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 maxDownloads = maxActiveDownloads();
|
||||||
int maxActive = maxActiveTorrents();
|
int maxActive = maxActiveTorrents();
|
||||||
|
|
||||||
@@ -1186,7 +1171,7 @@ void Session::initMetrics()
|
|||||||
|
|
||||||
void Session::configure(libtorrent::settings_pack &settingsPack)
|
void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||||
{
|
{
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QString chosenIP;
|
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_int(libt::settings_pack::allowed_enc_level, libt::settings_pack::pe_rc4);
|
||||||
settingsPack.set_bool(libt::settings_pack::prefer_rc4, true);
|
settingsPack.set_bool(libt::settings_pack::prefer_rc4, true);
|
||||||
switch (encryption()) {
|
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::out_enc_policy, libt::settings_pack::pe_enabled);
|
||||||
settingsPack.set_int(libt::settings_pack::in_enc_policy, libt::settings_pack::pe_enabled);
|
settingsPack.set_int(libt::settings_pack::in_enc_policy, libt::settings_pack::pe_enabled);
|
||||||
break;
|
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_trackers, announceToAllTrackers());
|
||||||
settingsPack.set_bool(libt::settings_pack::announce_to_all_tiers, announceToAllTiers());
|
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_size, cacheSize);
|
||||||
settingsPack.set_int(libt::settings_pack::cache_expiry, diskCacheTTL());
|
settingsPack.set_int(libt::settings_pack::cache_expiry, diskCacheTTL());
|
||||||
qDebug() << "Using a disk cache size of" << cacheSize << "MiB";
|
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_read_mode, mode);
|
||||||
settingsPack.set_int(libt::settings_pack::disk_io_write_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::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()
|
settingsPack.set_int(libt::settings_pack::suggest_mode, isSuggestModeEnabled()
|
||||||
? libt::settings_pack::suggest_read_cache : libt::settings_pack::no_piece_suggestions);
|
? 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_int(libt::settings_pack::active_seeds, maxActiveUploads());
|
||||||
settingsPack.set_bool(libt::settings_pack::dont_count_slow_torrents, ignoreSlowTorrentsForQueueing());
|
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 {
|
else {
|
||||||
settingsPack.set_int(libt::settings_pack::active_downloads, -1);
|
settingsPack.set_int(libt::settings_pack::active_downloads, -1);
|
||||||
@@ -1516,12 +1494,12 @@ void Session::configurePeerClasses()
|
|||||||
|
|
||||||
void Session::adjustLimits(libt::session_settings &sessionSettings)
|
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 maxDownloads = maxActiveDownloads();
|
||||||
int maxActive = maxActiveTorrents();
|
int maxActive = maxActiveTorrents();
|
||||||
|
|
||||||
sessionSettings.active_downloads = (maxDownloads > -1) ? (maxDownloads + m_extraLimit) : maxDownloads;
|
sessionSettings.active_downloads = maxDownloads > -1 ? maxDownloads + m_extraLimit : maxDownloads;
|
||||||
sessionSettings.active_limit = (maxActive > -1) ? (maxActive + m_extraLimit) : maxActive;
|
sessionSettings.active_limit = maxActive > -1 ? maxActive + m_extraLimit : maxActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::applyBandwidthLimits(libt::session_settings &sessionSettings)
|
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.allowed_enc_level = libt::pe_settings::rc4;
|
||||||
encryptionSettings.prefer_rc4 = true;
|
encryptionSettings.prefer_rc4 = true;
|
||||||
switch (encryption()) {
|
switch (encryption()) {
|
||||||
case 0: // Enabled
|
case 0: //Enabled
|
||||||
encryptionSettings.out_enc_policy = libt::pe_settings::enabled;
|
encryptionSettings.out_enc_policy = libt::pe_settings::enabled;
|
||||||
encryptionSettings.in_enc_policy = libt::pe_settings::enabled;
|
encryptionSettings.in_enc_policy = libt::pe_settings::enabled;
|
||||||
break;
|
break;
|
||||||
@@ -1595,7 +1573,7 @@ void Session::configure(libtorrent::session_settings &sessionSettings)
|
|||||||
|
|
||||||
sessionSettings.announce_to_all_trackers = announceToAllTrackers();
|
sessionSettings.announce_to_all_trackers = announceToAllTrackers();
|
||||||
sessionSettings.announce_to_all_tiers = announceToAllTiers();
|
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_size = cacheSize;
|
||||||
sessionSettings.cache_expiry = diskCacheTTL();
|
sessionSettings.cache_expiry = diskCacheTTL();
|
||||||
qDebug() << "Using a disk cache size of" << cacheSize << "MiB";
|
qDebug() << "Using a disk cache size of" << cacheSize << "MiB";
|
||||||
@@ -1768,7 +1746,7 @@ void Session::enableBandwidthScheduler()
|
|||||||
void Session::populateAdditionalTrackers()
|
void Session::populateAdditionalTrackers()
|
||||||
{
|
{
|
||||||
m_additionalTrackerList.clear();
|
m_additionalTrackerList.clear();
|
||||||
foreach (QString tracker, additionalTrackers().split('\n')) {
|
foreach (QString tracker, additionalTrackers().split("\n")) {
|
||||||
tracker = tracker.trimmed();
|
tracker = tracker.trimmed();
|
||||||
if (!tracker.isEmpty())
|
if (!tracker.isEmpty())
|
||||||
m_additionalTrackerList << tracker;
|
m_additionalTrackerList << tracker;
|
||||||
@@ -1792,7 +1770,7 @@ void Session::processShareLimits()
|
|||||||
qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
|
qDebug("Ratio: %f (limit: %f)", ratio, ratioLimit);
|
||||||
|
|
||||||
if ((ratio <= TorrentHandle::MAX_RATIO) && (ratio >= ratioLimit)) {
|
if ((ratio <= TorrentHandle::MAX_RATIO) && (ratio >= ratioLimit)) {
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
if (m_maxRatioAction == Remove) {
|
if (m_maxRatioAction == Remove) {
|
||||||
logger->addMessage(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
logger->addMessage(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash());
|
deleteTorrent(torrent->hash());
|
||||||
@@ -1801,7 +1779,6 @@ void Session::processShareLimits()
|
|||||||
torrent->pause();
|
torrent->pause();
|
||||||
logger->addMessage(tr("'%1' reached the maximum ratio you set. Paused.").arg(torrent->name()));
|
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);
|
qDebug("Seeding Time: %d (limit: %d)", seedingTimeInMinutes, seedingTimeLimit);
|
||||||
|
|
||||||
if ((seedingTimeInMinutes <= TorrentHandle::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit)) {
|
if ((seedingTimeInMinutes <= TorrentHandle::MAX_SEEDING_TIME) && (seedingTimeInMinutes >= seedingTimeLimit)) {
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
if (m_maxRatioAction == Remove) {
|
if (m_maxRatioAction == Remove) {
|
||||||
logger->addMessage(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
logger->addMessage(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash());
|
deleteTorrent(torrent->hash());
|
||||||
@@ -2010,8 +1987,8 @@ void Session::decreaseTorrentsPriority(const QStringList &hashes)
|
|||||||
torrentQueue.pop();
|
torrentQueue.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
foreach (const InfoHash &hash, m_loadedMetadata.keys())
|
||||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
torrentQueuePositionBottom(m_nativeSession->find_torrent(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::topTorrentsPriority(const QStringList &hashes)
|
void Session::topTorrentsPriority(const QStringList &hashes)
|
||||||
@@ -2055,8 +2032,8 @@ void Session::bottomTorrentsPriority(const QStringList &hashes)
|
|||||||
torrentQueue.pop();
|
torrentQueue.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
foreach (const InfoHash &hash, m_loadedMetadata.keys())
|
||||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
torrentQueuePositionBottom(m_nativeSession->find_torrent(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<InfoHash, TorrentHandle *> Session::torrents() const
|
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));
|
Logger::instance()->addMessage(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
|
||||||
// Launch downloader
|
// Launch downloader
|
||||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true);
|
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)
|
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleDownloadFinished(QString, QString)));
|
||||||
, this, &Session::handleDownloadFinished);
|
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleDownloadFailed(QString, QString)));
|
||||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &Session::handleDownloadFailed);
|
connect(handler, SIGNAL(redirectedToMagnet(QString, QString)), this, SLOT(handleRedirectedToMagnet(QString, QString)));
|
||||||
connect(handler, &Net::DownloadHandler::redirectedToMagnet, this, &Session::handleRedirectedToMagnet);
|
|
||||||
m_downloadedTorrents[handler->url()] = params;
|
m_downloadedTorrents[handler->url()] = params;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -2154,26 +2130,12 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
|||||||
p = magnetUri.addTorrentParams();
|
p = magnetUri.addTorrentParams();
|
||||||
}
|
}
|
||||||
else if (torrentInfo.isValid()) {
|
else if (torrentInfo.isValid()) {
|
||||||
if (!addData.resumed) {
|
if (!addData.resumed && !addData.hasRootFolder)
|
||||||
if (!addData.hasRootFolder)
|
torrentInfo.stripRootFolder();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
if (!addData.resumed && !addData.hasSeedStatus)
|
||||||
|
findIncompleteFiles(torrentInfo, savePath);
|
||||||
p.ti = torrentInfo.nativeInfo();
|
p.ti = torrentInfo.nativeInfo();
|
||||||
hash = torrentInfo.hash();
|
hash = torrentInfo.hash();
|
||||||
}
|
}
|
||||||
@@ -2279,7 +2241,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri)
|
|||||||
InfoHash hash = magnetUri.hash();
|
InfoHash hash = magnetUri.hash();
|
||||||
QString name = magnetUri.name();
|
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
|
// processed or adding to session
|
||||||
if (m_torrents.contains(hash)) return false;
|
if (m_torrents.contains(hash)) return false;
|
||||||
if (m_addingTorrents.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_connections = maxConnectionsPerTorrent();
|
||||||
p.max_uploads = maxUploadsPerTorrent();
|
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();
|
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||||
|
|
||||||
// Forced start
|
// Forced start
|
||||||
@@ -2375,7 +2337,9 @@ void Session::saveResumeData()
|
|||||||
std::vector<libt::alert *> alerts;
|
std::vector<libt::alert *> alerts;
|
||||||
getPendingAlerts(alerts, 30 * 1000);
|
getPendingAlerts(alerts, 30 * 1000);
|
||||||
if (alerts.empty()) {
|
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;
|
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);
|
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();
|
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
|
// 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()
|
const QStringList Session::getListeningIPs()
|
||||||
{
|
{
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
QStringList IPs;
|
QStringList IPs;
|
||||||
|
|
||||||
const QString ifaceName = networkInterface();
|
const QString ifaceName = networkInterface();
|
||||||
@@ -2493,12 +2457,12 @@ const QStringList Session::getListeningIPs()
|
|||||||
ip = entry.ip();
|
ip = entry.ip();
|
||||||
ipString = ip.toString();
|
ipString = ip.toString();
|
||||||
protocol = ip.protocol();
|
protocol = ip.protocol();
|
||||||
Q_ASSERT((protocol == QAbstractSocket::IPv4Protocol) || (protocol == QAbstractSocket::IPv6Protocol));
|
Q_ASSERT(protocol == QAbstractSocket::IPv4Protocol || protocol == QAbstractSocket::IPv6Protocol);
|
||||||
if ((!listenIPv6 && (protocol == QAbstractSocket::IPv6Protocol))
|
if ((!listenIPv6 && (protocol == QAbstractSocket::IPv6Protocol))
|
||||||
|| (listenIPv6 && (protocol == QAbstractSocket::IPv4Protocol)))
|
|| (listenIPv6 && (protocol == QAbstractSocket::IPv4Protocol)))
|
||||||
continue;
|
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.isEmpty()) {
|
||||||
if (ifaceAddr == ipString) {
|
if (ifaceAddr == ipString) {
|
||||||
IPs.append(ipString);
|
IPs.append(ipString);
|
||||||
@@ -2529,7 +2493,7 @@ void Session::configureListeningInterface()
|
|||||||
const ushort port = this->port();
|
const ushort port = this->port();
|
||||||
qDebug() << Q_FUNC_INFO << port;
|
qDebug() << Q_FUNC_INFO << port;
|
||||||
|
|
||||||
Logger *const logger = Logger::instance();
|
Logger* const logger = Logger::instance();
|
||||||
|
|
||||||
std::pair<int, int> ports(port, port);
|
std::pair<int, int> ports(port, port);
|
||||||
libt::error_code ec;
|
libt::error_code ec;
|
||||||
@@ -2965,7 +2929,7 @@ void Session::setMaxConnectionsPerTorrent(int max)
|
|||||||
try {
|
try {
|
||||||
handle.set_max_connections(max);
|
handle.set_max_connections(max);
|
||||||
}
|
}
|
||||||
catch (const std::exception &) {}
|
catch (std::exception) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2987,7 +2951,7 @@ void Session::setMaxUploadsPerTorrent(int max)
|
|||||||
try {
|
try {
|
||||||
handle.set_max_uploads(max);
|
handle.set_max_uploads(max);
|
||||||
}
|
}
|
||||||
catch (const std::exception &) {}
|
catch (std::exception) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3085,19 +3049,6 @@ void Session::setGuidedReadCacheEnabled(bool enabled)
|
|||||||
configureDeferred();
|
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
|
bool Session::isSuggestModeEnabled() const
|
||||||
{
|
{
|
||||||
return m_isSuggestMode;
|
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
|
int Session::outgoingPortsMin() const
|
||||||
{
|
{
|
||||||
return m_outgoingPortsMin;
|
return m_outgoingPortsMin;
|
||||||
@@ -3532,7 +3441,7 @@ void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QStrin
|
|||||||
emit torrentTagRemoved(torrent, tag);
|
emit torrentTagRemoved(torrent, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent)
|
void Session::handleTorrentSavingModeChanged(TorrentHandle * const torrent)
|
||||||
{
|
{
|
||||||
emit torrentSavingModeChanged(torrent);
|
emit torrentSavingModeChanged(torrent);
|
||||||
}
|
}
|
||||||
@@ -3540,7 +3449,7 @@ void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent)
|
|||||||
void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QList<TrackerEntry> &newTrackers)
|
void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QList<TrackerEntry> &newTrackers)
|
||||||
{
|
{
|
||||||
foreach (const TrackerEntry &newTracker, 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);
|
emit trackersAdded(torrent, newTrackers);
|
||||||
if (torrent->trackers().size() == newTrackers.size())
|
if (torrent->trackers().size() == newTrackers.size())
|
||||||
emit trackerlessStateChanged(torrent, false);
|
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)
|
void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const QList<TrackerEntry> &deletedTrackers)
|
||||||
{
|
{
|
||||||
foreach (const TrackerEntry &deletedTracker, 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);
|
emit trackersRemoved(torrent, deletedTrackers);
|
||||||
if (torrent->trackers().size() == 0)
|
if (torrent->trackers().size() == 0)
|
||||||
emit trackerlessStateChanged(torrent, true);
|
emit trackerlessStateChanged(torrent, true);
|
||||||
@@ -3565,13 +3474,13 @@ void Session::handleTorrentTrackersChanged(TorrentHandle *const torrent)
|
|||||||
void Session::handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QList<QUrl> &newUrlSeeds)
|
void Session::handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QList<QUrl> &newUrlSeeds)
|
||||||
{
|
{
|
||||||
foreach (const QUrl &newUrlSeed, 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)
|
void Session::handleTorrentUrlSeedsRemoved(TorrentHandle *const torrent, const QList<QUrl> &urlSeeds)
|
||||||
{
|
{
|
||||||
foreach (const QUrl &urlSeed, 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)
|
void Session::handleTorrentMetadataReceived(TorrentHandle *const torrent)
|
||||||
@@ -3734,8 +3643,8 @@ void Session::enableIPFilter()
|
|||||||
// set between clearing the old one and setting the new one.
|
// set between clearing the old one and setting the new one.
|
||||||
if (!m_filterParser) {
|
if (!m_filterParser) {
|
||||||
m_filterParser = new FilterParserThread(this);
|
m_filterParser = new FilterParserThread(this);
|
||||||
connect(m_filterParser.data(), &FilterParserThread::IPFilterParsed, this, &Session::handleIPFilterParsed);
|
connect(m_filterParser.data(), SIGNAL(IPFilterParsed(int)), SLOT(handleIPFilterParsed(int)));
|
||||||
connect(m_filterParser.data(), &FilterParserThread::IPFilterError, this, &Session::handleIPFilterError);
|
connect(m_filterParser.data(), SIGNAL(IPFilterError()), SLOT(handleIPFilterError()));
|
||||||
}
|
}
|
||||||
m_filterParser->processFilterFile(IPFilterFile());
|
m_filterParser->processFilterFile(IPFilterFile());
|
||||||
}
|
}
|
||||||
@@ -3768,7 +3677,7 @@ void Session::recursiveTorrentDownload(const InfoHash &hash)
|
|||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
tr("Recursive download of file '%1' embedded in torrent '%2'"
|
tr("Recursive download of file '%1' embedded in torrent '%2'"
|
||||||
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
|
, "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;
|
const QString torrentFullpath = torrent->savePath() + "/" + torrentRelpath;
|
||||||
|
|
||||||
AddTorrentParams params;
|
AddTorrentParams params;
|
||||||
@@ -3818,7 +3727,7 @@ void Session::startUpTorrents()
|
|||||||
.arg(params.hash), Log::CRITICAL);
|
.arg(params.hash), Log::CRITICAL);
|
||||||
|
|
||||||
// process add torrent messages before message queue overflow
|
// process add torrent messages before message queue overflow
|
||||||
if ((resumedTorrentsCount % 100) == 0) readAlerts();
|
if (resumedTorrentsCount % 100 == 0) readAlerts();
|
||||||
|
|
||||||
++resumedTorrentsCount;
|
++resumedTorrentsCount;
|
||||||
};
|
};
|
||||||
@@ -3853,12 +3762,12 @@ void Session::startUpTorrents()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int q = queuePosition;
|
int q = queuePosition;
|
||||||
for (; queuedResumeData.contains(q); ++q) {
|
for(; queuedResumeData.contains(q); ++q) {
|
||||||
}
|
}
|
||||||
if (q != queuePosition) {
|
if (q != queuePosition) {
|
||||||
++numOfRemappedFiles;
|
++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);
|
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...")
|
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()))
|
.arg(tmpRemovingTorrentData.name)
|
||||||
, Log::CRITICAL);
|
.arg(QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleMetadataReceivedAlert(libt::metadata_received_alert *p)
|
void Session::handleMetadataReceivedAlert(libt::metadata_received_alert *p)
|
||||||
@@ -4189,15 +4098,10 @@ void Session::handleFileErrorAlert(libt::file_error_alert *p)
|
|||||||
// NOTE: Check this function!
|
// NOTE: Check this function!
|
||||||
TorrentHandle *const torrent = m_torrents.value(p->handle.info_hash());
|
TorrentHandle *const torrent = m_torrents.value(p->handle.info_hash());
|
||||||
if (torrent) {
|
if (torrent) {
|
||||||
const InfoHash hash = torrent->hash();
|
QString msg = QString::fromStdString(p->message());
|
||||||
if (!m_recentErroredTorrents.contains(hash)) {
|
Logger::instance()->addMessage(tr("An I/O error occurred, '%1' paused. %2")
|
||||||
m_recentErroredTorrents.insert(hash);
|
.arg(torrent->name()).arg(msg));
|
||||||
const QString msg = QString::fromStdString(p->message());
|
emit fullDiskError(torrent, msg);
|
||||||
LogMsg(tr("An I/O error occurred, '%1' paused. %2").arg(torrent->name(), msg));
|
|
||||||
emit fullDiskError(torrent, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_recentErroredTorrentsTimer->start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4272,9 +4176,7 @@ void Session::handleListenSucceededAlert(libt::listen_succeeded_alert *p)
|
|||||||
else if (p->sock_type == libt::listen_succeeded_alert::tcp_ssl)
|
else if (p->sock_type == libt::listen_succeeded_alert::tcp_ssl)
|
||||||
proto = "TCP_SSL";
|
proto = "TCP_SSL";
|
||||||
qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||||
Logger::instance()->addMessage(
|
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);
|
||||||
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);
|
|
||||||
|
|
||||||
// Force reannounce on all torrents because some trackers blacklist some ports
|
// Force reannounce on all torrents because some trackers blacklist some ports
|
||||||
std::vector<libt::torrent_handle> torrents = m_nativeSession->get_torrents();
|
std::vector<libt::torrent_handle> torrents = m_nativeSession->get_torrents();
|
||||||
@@ -4300,11 +4202,10 @@ void Session::handleListenFailedAlert(libt::listen_failed_alert *p)
|
|||||||
proto = "SOCKS5";
|
proto = "SOCKS5";
|
||||||
qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4.",
|
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.")
|
"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())
|
.arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port()))
|
||||||
, QString::fromLocal8Bit(p->error.message().c_str()))
|
.arg(QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||||
, Log::CRITICAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleExternalIPAlert(libt::external_ip_alert *p)
|
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.diskWriteQueue = p->values[m_metricIndices.peer.numPeersDownDisk];
|
||||||
m_status.peersCount = p->values[m_metricIndices.peer.numPeersConnected];
|
m_status.peersCount = p->values[m_metricIndices.peer.numPeersConnected];
|
||||||
|
|
||||||
const int numBlocksRead = p->values[m_metricIndices.disk.numBlocksRead];
|
const auto numBlocksRead = p->values[m_metricIndices.disk.numBlocksRead];
|
||||||
const int numBlocksCacheHits = p->values[m_metricIndices.disk.numBlocksCacheHits];
|
|
||||||
m_cacheStatus.totalUsedBuffers = p->values[m_metricIndices.disk.diskBlocksInUse];
|
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];
|
m_cacheStatus.jobQueueLength = p->values[m_metricIndices.disk.queuedDiskJobs];
|
||||||
|
|
||||||
quint64 totalJobs = p->values[m_metricIndices.disk.writeJobs] + p->values[m_metricIndices.disk.readJobs]
|
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
|
return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
|
||||||
|
|
||||||
using PCONVERTIFACENAMETOLUID = NETIO_STATUS (WINAPI *)(const WCHAR *, PNET_LUID);
|
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();
|
if (!ConvertIfaceNameToLuid) return QString();
|
||||||
|
|
||||||
using PCONVERTIFACELUIDTOGUID = NETIO_STATUS (WINAPI *)(const NET_LUID *, GUID *);
|
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();
|
if (!ConvertIfaceLuidToGuid) return QString();
|
||||||
|
|
||||||
NET_LUID luid;
|
NET_LUID luid;
|
||||||
|
|||||||
@@ -30,14 +30,18 @@
|
|||||||
#ifndef BITTORRENT_SESSION_H
|
#ifndef BITTORRENT_SESSION_H
|
||||||
#define BITTORRENT_SESSION_H
|
#define BITTORRENT_SESSION_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
#include <libtorrent/version.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
|
|
||||||
#include <vector>
|
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#endif
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QList>
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
|
#include <QMutex>
|
||||||
|
#endif
|
||||||
#include <QNetworkConfigurationManager>
|
#include <QNetworkConfigurationManager>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
@@ -45,12 +49,6 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
|
||||||
#if LIBTORRENT_VERSION_NUM < 10100
|
|
||||||
#include <QMutex>
|
|
||||||
#else
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
#include "base/tristatebool.h"
|
#include "base/tristatebool.h"
|
||||||
#include "base/types.h"
|
#include "base/types.h"
|
||||||
@@ -113,6 +111,7 @@ class QTimer;
|
|||||||
class QStringList;
|
class QStringList;
|
||||||
class QString;
|
class QString;
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
template<typename T> class QList;
|
||||||
|
|
||||||
class FilterParserThread;
|
class FilterParserThread;
|
||||||
class BandwidthScheduler;
|
class BandwidthScheduler;
|
||||||
@@ -383,8 +382,6 @@ namespace BitTorrent
|
|||||||
void setUseOSCache(bool use);
|
void setUseOSCache(bool use);
|
||||||
bool isGuidedReadCacheEnabled() const;
|
bool isGuidedReadCacheEnabled() const;
|
||||||
void setGuidedReadCacheEnabled(bool enabled);
|
void setGuidedReadCacheEnabled(bool enabled);
|
||||||
bool isCoalesceReadWriteEnabled() const;
|
|
||||||
void setCoalesceReadWriteEnabled(bool enabled);
|
|
||||||
bool isSuggestModeEnabled() const;
|
bool isSuggestModeEnabled() const;
|
||||||
void setSuggestMode(bool mode);
|
void setSuggestMode(bool mode);
|
||||||
int sendBufferWatermark() const;
|
int sendBufferWatermark() const;
|
||||||
@@ -399,12 +396,6 @@ namespace BitTorrent
|
|||||||
void setQueueingSystemEnabled(bool enabled);
|
void setQueueingSystemEnabled(bool enabled);
|
||||||
bool ignoreSlowTorrentsForQueueing() const;
|
bool ignoreSlowTorrentsForQueueing() const;
|
||||||
void setIgnoreSlowTorrentsForQueueing(bool ignore);
|
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;
|
int outgoingPortsMin() const;
|
||||||
void setOutgoingPortsMin(int min);
|
void setOutgoingPortsMin(int min);
|
||||||
int outgoingPortsMax() const;
|
int outgoingPortsMax() const;
|
||||||
@@ -563,7 +554,7 @@ namespace BitTorrent
|
|||||||
bool requestedFileDeletion;
|
bool requestedFileDeletion;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Session(QObject *parent = nullptr);
|
explicit Session(QObject *parent = 0);
|
||||||
~Session();
|
~Session();
|
||||||
|
|
||||||
bool hasPerTorrentRatioLimit() const;
|
bool hasPerTorrentRatioLimit() const;
|
||||||
@@ -655,7 +646,6 @@ namespace BitTorrent
|
|||||||
CachedSettingValue<int> m_diskCacheTTL;
|
CachedSettingValue<int> m_diskCacheTTL;
|
||||||
CachedSettingValue<bool> m_useOSCache;
|
CachedSettingValue<bool> m_useOSCache;
|
||||||
CachedSettingValue<bool> m_guidedReadCacheEnabled;
|
CachedSettingValue<bool> m_guidedReadCacheEnabled;
|
||||||
CachedSettingValue<bool> m_coalesceReadWriteEnabled;
|
|
||||||
CachedSettingValue<bool> m_isSuggestMode;
|
CachedSettingValue<bool> m_isSuggestMode;
|
||||||
CachedSettingValue<int> m_sendBufferWatermark;
|
CachedSettingValue<int> m_sendBufferWatermark;
|
||||||
CachedSettingValue<int> m_sendBufferLowWatermark;
|
CachedSettingValue<int> m_sendBufferLowWatermark;
|
||||||
@@ -666,9 +656,6 @@ namespace BitTorrent
|
|||||||
CachedSettingValue<int> m_maxActiveUploads;
|
CachedSettingValue<int> m_maxActiveUploads;
|
||||||
CachedSettingValue<int> m_maxActiveTorrents;
|
CachedSettingValue<int> m_maxActiveTorrents;
|
||||||
CachedSettingValue<bool> m_ignoreSlowTorrentsForQueueing;
|
CachedSettingValue<bool> m_ignoreSlowTorrentsForQueueing;
|
||||||
CachedSettingValue<int> m_downloadRateForSlowTorrents;
|
|
||||||
CachedSettingValue<int> m_uploadRateForSlowTorrents;
|
|
||||||
CachedSettingValue<int> m_slowTorrentsInactivityTimer;
|
|
||||||
CachedSettingValue<int> m_outgoingPortsMin;
|
CachedSettingValue<int> m_outgoingPortsMin;
|
||||||
CachedSettingValue<int> m_outgoingPortsMax;
|
CachedSettingValue<int> m_outgoingPortsMax;
|
||||||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||||
@@ -761,10 +748,6 @@ namespace BitTorrent
|
|||||||
QStringMap m_categories;
|
QStringMap m_categories;
|
||||||
QSet<QString> m_tags;
|
QSet<QString> m_tags;
|
||||||
|
|
||||||
// I/O errored torrents
|
|
||||||
QSet<InfoHash> m_recentErroredTorrents;
|
|
||||||
QTimer *m_recentErroredTorrentsTimer;
|
|
||||||
|
|
||||||
#if LIBTORRENT_VERSION_NUM < 10100
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
QMutex m_alertsMutex;
|
QMutex m_alertsMutex;
|
||||||
QWaitCondition m_alertsWaitCondition;
|
QWaitCondition m_alertsWaitCondition;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "torrentcreatorthread.h"
|
#include "torrentcreatorthread.h"
|
||||||
@@ -35,14 +37,9 @@
|
|||||||
#include <libtorrent/create_torrent.hpp>
|
#include <libtorrent/create_torrent.hpp>
|
||||||
#include <libtorrent/storage.hpp>
|
#include <libtorrent/storage.hpp>
|
||||||
#include <libtorrent/torrent_info.hpp>
|
#include <libtorrent/torrent_info.hpp>
|
||||||
#include <libtorrent/version.hpp>
|
|
||||||
|
|
||||||
#include <QDirIterator>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
#include "base/global.h"
|
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
@@ -89,56 +86,13 @@ void TorrentCreatorThread::run()
|
|||||||
emit updateProgress(0);
|
emit updateProgress(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + "/";
|
|
||||||
|
|
||||||
// Adding files to the torrent
|
|
||||||
libt::file_storage fs;
|
libt::file_storage fs;
|
||||||
if (QFileInfo(m_params.inputPath).isFile()) {
|
// Adding files to the torrent
|
||||||
libt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
|
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInterruptionRequested()) return;
|
if (isInterruptionRequested()) return;
|
||||||
|
|
||||||
#if LIBTORRENT_VERSION_NUM < 10100
|
libt::create_torrent newTorrent(fs, m_params.pieceSize);
|
||||||
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
|
|
||||||
|
|
||||||
// Add url seeds
|
// Add url seeds
|
||||||
foreach (QString seed, m_params.urlSeeds) {
|
foreach (QString seed, m_params.urlSeeds) {
|
||||||
@@ -158,6 +112,7 @@ void TorrentCreatorThread::run()
|
|||||||
if (isInterruptionRequested()) return;
|
if (isInterruptionRequested()) return;
|
||||||
|
|
||||||
// calculate the hash for all pieces
|
// 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()
|
libt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
|
||||||
, [this, &newTorrent](const int n) { sendProgressSignal(n, newTorrent.num_pieces()); });
|
, [this, &newTorrent](const int n) { sendProgressSignal(n, newTorrent.num_pieces()); });
|
||||||
// Set qBittorrent as creator and add user comment to
|
// 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())
|
if (inputPath.isEmpty())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
libt::file_storage fs;
|
libt::file_storage fs;
|
||||||
libt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter);
|
libt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter);
|
||||||
|
return libt::create_torrent(fs, pieceSize).num_pieces();
|
||||||
#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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef BITTORRENT_TORRENTCREATORTHREAD_H
|
#ifndef BITTORRENT_TORRENTCREATORTHREAD_H
|
||||||
@@ -37,7 +39,6 @@ namespace BitTorrent
|
|||||||
struct TorrentCreatorParams
|
struct TorrentCreatorParams
|
||||||
{
|
{
|
||||||
bool isPrivate;
|
bool isPrivate;
|
||||||
bool isAlignmentOptimized;
|
|
||||||
int pieceSize;
|
int pieceSize;
|
||||||
QString inputPath;
|
QString inputPath;
|
||||||
QString savePath;
|
QString savePath;
|
||||||
@@ -57,7 +58,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
void create(const TorrentCreatorParams ¶ms);
|
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:
|
protected:
|
||||||
void run();
|
void run();
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
#include "session.h"
|
#include "session.h"
|
||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
|
|
||||||
const QString QB_EXT {QStringLiteral(".!qB")};
|
const QString QB_EXT {".!qB"};
|
||||||
|
|
||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
using namespace BitTorrent;
|
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.
|
// The following can be removed after one or two libtorrent releases on each branch.
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
const char i18nContext[] = "TorrentHandle";
|
||||||
|
|
||||||
// new constructor is available
|
// new constructor is available
|
||||||
template<typename T, typename std::enable_if<std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
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)
|
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||||
@@ -519,19 +521,15 @@ int TorrentHandle::piecesHave() const
|
|||||||
|
|
||||||
qreal TorrentHandle::progress() const
|
qreal TorrentHandle::progress() const
|
||||||
{
|
{
|
||||||
if (!isChecking()) {
|
if (!m_nativeStatus.total_wanted)
|
||||||
if (!m_nativeStatus.total_wanted)
|
return 0.;
|
||||||
return 0.;
|
|
||||||
|
|
||||||
if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
|
if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
|
||||||
return 1.;
|
return 1.;
|
||||||
|
|
||||||
qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
|
float progress = static_cast<float>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
|
||||||
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
|
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
|
||||||
return progress;
|
return progress;
|
||||||
}
|
|
||||||
|
|
||||||
return m_nativeStatus.progress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentHandle::category() const
|
QString TorrentHandle::category() const
|
||||||
@@ -738,8 +736,7 @@ bool TorrentHandle::isActive() const
|
|||||||
|| m_state == TorrentState::Downloading
|
|| m_state == TorrentState::Downloading
|
||||||
|| m_state == TorrentState::ForcedDownloading
|
|| m_state == TorrentState::ForcedDownloading
|
||||||
|| m_state == TorrentState::Uploading
|
|| m_state == TorrentState::Uploading
|
||||||
|| m_state == TorrentState::ForcedUploading
|
|| m_state == TorrentState::ForcedUploading;
|
||||||
|| m_state == TorrentState::Moving;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentHandle::isInactive() const
|
bool TorrentHandle::isInactive() const
|
||||||
@@ -781,18 +778,28 @@ bool TorrentHandle::hasFirstLastPiecePriority() const
|
|||||||
if (!hasMetadata())
|
if (!hasMetadata())
|
||||||
return m_needsToSetFirstLastPiecePriority;
|
return m_needsToSetFirstLastPiecePriority;
|
||||||
|
|
||||||
const std::vector<int> filePriorities = nativeHandle().file_priorities();
|
// Get int first media file
|
||||||
for (int i = 0; i < static_cast<int>(filePriorities.size()); ++i) {
|
std::vector<int> fp;
|
||||||
if (filePriorities[i] <= 0)
|
fp = m_nativeHandle.file_priorities();
|
||||||
continue;
|
|
||||||
|
|
||||||
const TorrentInfo::PieceRange extremities = info().filePieces(i);
|
TorrentInfo::PieceRange extremities;
|
||||||
const int firstPiecePrio = nativeHandle().piece_priority(extremities.first());
|
bool found = false;
|
||||||
const int lastPiecePrio = nativeHandle().piece_priority(extremities.last());
|
int count = static_cast<int>(fp.size());
|
||||||
return ((firstPiecePrio == 7) && (lastPiecePrio == 7));
|
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
|
TorrentState TorrentHandle::state() const
|
||||||
@@ -802,10 +809,7 @@ TorrentState TorrentHandle::state() const
|
|||||||
|
|
||||||
void TorrentHandle::updateState()
|
void TorrentHandle::updateState()
|
||||||
{
|
{
|
||||||
if (isMoveInProgress()) {
|
if (isPaused()) {
|
||||||
m_state = TorrentState::Moving;
|
|
||||||
}
|
|
||||||
else if (isPaused()) {
|
|
||||||
if (hasMissingFiles())
|
if (hasMissingFiles())
|
||||||
m_state = TorrentState::MissingFiles;
|
m_state = TorrentState::MissingFiles;
|
||||||
else if (hasError())
|
else if (hasError())
|
||||||
@@ -1273,7 +1277,7 @@ void TorrentHandle::forceRecheck()
|
|||||||
|
|
||||||
if (isPaused()) {
|
if (isPaused()) {
|
||||||
m_pauseAfterRecheck = true;
|
m_pauseAfterRecheck = true;
|
||||||
resume_impl(true, true);
|
resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_nativeHandle.force_recheck();
|
m_nativeHandle.force_recheck();
|
||||||
@@ -1292,37 +1296,39 @@ void TorrentHandle::toggleSequentialDownload()
|
|||||||
setSequentialDownload(!isSequentialDownload());
|
setSequentialDownload(!isSequentialDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::setFirstLastPiecePriority(const bool enabled)
|
void TorrentHandle::setFirstLastPiecePriority(bool b)
|
||||||
{
|
{
|
||||||
if (!hasMetadata()) {
|
if (!hasMetadata()) {
|
||||||
m_needsToSetFirstLastPiecePriority = enabled;
|
m_needsToSetFirstLastPiecePriority = b;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download first and last pieces first for every file in the torrent
|
std::vector<int> fp = m_nativeHandle.file_priorities();
|
||||||
const std::vector<int> filePriorities = nativeHandle().file_priorities();
|
std::vector<int> pp = m_nativeHandle.piece_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;
|
|
||||||
|
|
||||||
// Determine the priority to set
|
// Download first and last pieces first for all media files in the torrent
|
||||||
const int newPrio = enabled ? 7 : filePrio;
|
int nbfiles = static_cast<int>(fp.size());
|
||||||
const TorrentInfo::PieceRange extremities = info().filePieces(index);
|
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)
|
// Determine the priority to set
|
||||||
const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
|
int prio = b ? 7 : fp[index];
|
||||||
for (int i = 0; i < nNumPieces; ++i) {
|
|
||||||
piecePriorities[extremities.first() + i] = newPrio;
|
TorrentInfo::PieceRange extremities = info().filePieces(index);
|
||||||
piecePriorities[extremities.last() - i] = newPrio;
|
|
||||||
|
// 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);
|
m_nativeHandle.prioritize_pieces(pp);
|
||||||
|
|
||||||
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
|
||||||
.arg((enabled ? tr("On") : tr("Off")), name()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::toggleFirstLastPiecePriority()
|
void TorrentHandle::toggleFirstLastPiecePriority()
|
||||||
@@ -1339,17 +1345,12 @@ void TorrentHandle::pause()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::resume(bool forced)
|
void TorrentHandle::resume(bool forced)
|
||||||
{
|
|
||||||
resume_impl(forced, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TorrentHandle::resume_impl(bool forced, bool uploadMode)
|
|
||||||
{
|
{
|
||||||
if (hasError())
|
if (hasError())
|
||||||
m_nativeHandle.clear_error();
|
m_nativeHandle.clear_error();
|
||||||
m_hasMissingFiles = false;
|
m_hasMissingFiles = false;
|
||||||
|
m_nativeHandle.set_upload_mode(false);
|
||||||
m_nativeHandle.auto_managed(!forced);
|
m_nativeHandle.auto_managed(!forced);
|
||||||
m_nativeHandle.set_upload_mode(uploadMode);
|
|
||||||
m_nativeHandle.resume();
|
m_nativeHandle.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,7 +1371,6 @@ void TorrentHandle::moveStorage(const QString &newPath, bool overwrite)
|
|||||||
, (overwrite ? libt::always_replace_files : libt::dont_replace));
|
, (overwrite ? libt::always_replace_files : libt::dont_replace));
|
||||||
m_moveStorageInfo.oldPath = oldPath;
|
m_moveStorageInfo.oldPath = oldPath;
|
||||||
m_moveStorageInfo.newPath = newPath;
|
m_moveStorageInfo.newPath = newPath;
|
||||||
updateState();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1420,7 +1420,7 @@ void TorrentHandle::handleStateUpdate(const libt::torrent_status &nativeStatus)
|
|||||||
updateStatus(nativeStatus);
|
updateStatus(nativeStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_alert *p)
|
void TorrentHandle::handleStorageMovedAlert(libtorrent::storage_moved_alert *p)
|
||||||
{
|
{
|
||||||
if (!isMoveInProgress()) {
|
if (!isMoveInProgress()) {
|
||||||
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
||||||
@@ -1437,8 +1437,8 @@ void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_aler
|
|||||||
return;
|
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};
|
const QDir oldDir {m_moveStorageInfo.oldPath};
|
||||||
if ((oldDir == QDir(m_session->torrentTempPath(info())))
|
if ((oldDir == QDir(m_session->torrentTempPath(info())))
|
||||||
&& (oldDir != QDir(m_session->tempPath()))) {
|
&& (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;
|
qDebug() << "Removing torrent temp folder:" << m_moveStorageInfo.oldPath;
|
||||||
Utils::Fs::smartRemoveEmptyFolderTree(m_moveStorageInfo.oldPath);
|
Utils::Fs::smartRemoveEmptyFolderTree(m_moveStorageInfo.oldPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_moveStorageInfo.newPath.clear();
|
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
|
||||||
|
m_moveStorageInfo.newPath.clear();
|
||||||
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
||||||
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
||||||
m_moveStorageInfo.queuedPath.clear();
|
m_moveStorageInfo.queuedPath.clear();
|
||||||
@@ -1465,19 +1464,17 @@ void TorrentHandle::handleStorageMovedAlert(const libtorrent::storage_moved_aler
|
|||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleStorageMovedFailedAlert(const libtorrent::storage_moved_failed_alert *p)
|
void TorrentHandle::handleStorageMovedFailedAlert(libtorrent::storage_moved_failed_alert *p)
|
||||||
{
|
{
|
||||||
if (!isMoveInProgress()) {
|
if (!isMoveInProgress()) {
|
||||||
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogMsg(tr("Could not move torrent: '%1'. Reason: %2")
|
LogMsg(QCoreApplication::translate(i18nContext, "Could not move torrent: '%1'. Reason: %2")
|
||||||
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
|
.arg(name()).arg(QString::fromStdString(p->message())), Log::CRITICAL);
|
||||||
|
|
||||||
m_moveStorageInfo.newPath.clear();
|
m_moveStorageInfo.newPath.clear();
|
||||||
updateStatus();
|
|
||||||
|
|
||||||
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
if (!m_moveStorageInfo.queuedPath.isEmpty()) {
|
||||||
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
|
||||||
m_moveStorageInfo.queuedPath.clear();
|
m_moveStorageInfo.queuedPath.clear();
|
||||||
@@ -1487,7 +1484,7 @@ void TorrentHandle::handleStorageMovedFailedAlert(const libtorrent::storage_move
|
|||||||
m_moveFinishedTriggers.takeFirst()();
|
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
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
QString trackerUrl = QString::fromStdString(p->url);
|
QString trackerUrl = QString::fromStdString(p->url);
|
||||||
@@ -1502,32 +1499,32 @@ void TorrentHandle::handleTrackerReplyAlert(const libtorrent::tracker_reply_aler
|
|||||||
m_session->handleTorrentTrackerReply(this, trackerUrl);
|
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
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
const QString trackerUrl = QString::fromStdString(p->url);
|
QString trackerUrl = QString::fromStdString(p->url);
|
||||||
const QString message = QString::fromStdString(p->msg);
|
QString message = QString::fromStdString(p->msg);
|
||||||
#else
|
#else
|
||||||
const QString trackerUrl = p->tracker_url();
|
QString trackerUrl(p->tracker_url());
|
||||||
const QString message = p->warning_message();
|
QString message = QString::fromStdString(p->message());
|
||||||
#endif
|
#endif
|
||||||
|
qDebug("Received a tracker warning for %s: %s", qUtf8Printable(trackerUrl), qUtf8Printable(message));
|
||||||
// Connection was successful now but there is a warning message
|
// Connection was successful now but there is a warning message
|
||||||
m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message
|
m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message
|
||||||
|
|
||||||
m_session->handleTorrentTrackerWarning(this, trackerUrl);
|
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
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
const QString trackerUrl = QString::fromStdString(p->url);
|
QString trackerUrl = QString::fromStdString(p->url);
|
||||||
const QString message = QString::fromStdString(p->msg);
|
QString message = QString::fromStdString(p->msg);
|
||||||
#else
|
#else
|
||||||
const QString trackerUrl = p->tracker_url();
|
QString trackerUrl(p->tracker_url());
|
||||||
const QString message = p->error_message();
|
QString message = QString::fromStdString(p->message());
|
||||||
#endif
|
#endif
|
||||||
|
qDebug("Received a tracker error for %s: %s", qUtf8Printable(trackerUrl), qUtf8Printable(message));
|
||||||
m_trackerInfos[trackerUrl].lastMessage = message;
|
m_trackerInfos[trackerUrl].lastMessage = message;
|
||||||
|
|
||||||
if (p->status_code == 401)
|
if (p->status_code == 401)
|
||||||
@@ -1536,7 +1533,7 @@ void TorrentHandle::handleTrackerErrorAlert(const libtorrent::tracker_error_aler
|
|||||||
m_session->handleTorrentTrackerError(this, trackerUrl);
|
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);
|
Q_UNUSED(p);
|
||||||
qDebug("%s have just finished checking", qUtf8Printable(hash()));
|
qDebug("%s have just finished checking", qUtf8Printable(hash()));
|
||||||
@@ -1559,7 +1556,7 @@ void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_
|
|||||||
m_session->handleTorrentChecked(this);
|
m_session->handleTorrentChecked(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p)
|
void TorrentHandle::handleTorrentFinishedAlert(libtorrent::torrent_finished_alert *p)
|
||||||
{
|
{
|
||||||
Q_UNUSED(p);
|
Q_UNUSED(p);
|
||||||
qDebug("Got a torrent finished alert for %s", qUtf8Printable(name()));
|
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);
|
Q_UNUSED(p);
|
||||||
updateStatus();
|
updateStatus();
|
||||||
@@ -1594,13 +1591,13 @@ void TorrentHandle::handleTorrentPausedAlert(const libtorrent::torrent_paused_al
|
|||||||
m_session->handleTorrentPaused(this);
|
m_session->handleTorrentPaused(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p)
|
void TorrentHandle::handleTorrentResumedAlert(libtorrent::torrent_resumed_alert *p)
|
||||||
{
|
{
|
||||||
Q_UNUSED(p);
|
Q_UNUSED(p);
|
||||||
m_session->handleTorrentResumed(this);
|
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);
|
const bool useDummyResumeData = !(p && p->resume_data);
|
||||||
libtorrent::entry dummyEntry;
|
libtorrent::entry dummyEntry;
|
||||||
@@ -1634,35 +1631,36 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
|
|||||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
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
|
// if torrent has no metadata we should save dummy fastresume data
|
||||||
// containing Magnet URI and qBittorrent own resume data only
|
// containing Magnet URI and qBittorrent own resume data only
|
||||||
if (p->error.value() == libt::errors::no_metadata)
|
if (p->error.value() == libt::errors::no_metadata)
|
||||||
handleSaveResumeDataAlert(nullptr);
|
handleSaveResumeDataAlert(0);
|
||||||
else
|
else
|
||||||
m_session->handleTorrentResumeDataFailed(this);
|
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());
|
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
|
||||||
|
Logger *const logger = Logger::instance();
|
||||||
|
|
||||||
updateStatus();
|
updateStatus();
|
||||||
if (p->error.value() == libt::errors::mismatching_file_size) {
|
if (p->error.value() == libt::errors::mismatching_file_size) {
|
||||||
// Mismatching file size (files were probably moved)
|
// 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;
|
m_hasMissingFiles = true;
|
||||||
if (!isPaused())
|
if (!isPaused())
|
||||||
pause();
|
pause();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
logger->addMessage(QCoreApplication::translate(i18nContext, "Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
||||||
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
|
.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
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name));
|
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("/");
|
QString newPath = newPathParts.join("/");
|
||||||
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
|
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
|
||||||
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(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));
|
qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath));
|
||||||
QDir().rmpath(oldPath);
|
QDir().rmpath(oldPath);
|
||||||
}
|
}
|
||||||
@@ -1694,7 +1692,7 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
|
|||||||
m_moveFinishedTriggers.takeFirst()();
|
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);
|
Q_UNUSED(p);
|
||||||
|
|
||||||
@@ -1703,7 +1701,7 @@ void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_fa
|
|||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p)
|
void TorrentHandle::handleFileCompletedAlert(libtorrent::file_completed_alert *p)
|
||||||
{
|
{
|
||||||
updateStatus();
|
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);
|
Q_ASSERT(p->interval >= 1000);
|
||||||
SpeedSample transferred(p->transferred[libt::stats_alert::download_payload] * 1000LL / p->interval,
|
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);
|
m_speedMonitor.addSample(transferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentHandle::handleMetadataReceivedAlert(const libt::metadata_received_alert *p)
|
void TorrentHandle::handleMetadataReceivedAlert(libt::metadata_received_alert *p)
|
||||||
{
|
{
|
||||||
Q_UNUSED(p);
|
Q_UNUSED(p);
|
||||||
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
||||||
|
|||||||
@@ -149,8 +149,6 @@ namespace BitTorrent
|
|||||||
PausedDownloading,
|
PausedDownloading,
|
||||||
PausedUploading,
|
PausedUploading,
|
||||||
|
|
||||||
Moving,
|
|
||||||
|
|
||||||
MissingFiles,
|
MissingFiles,
|
||||||
Error
|
Error
|
||||||
};
|
};
|
||||||
@@ -333,7 +331,7 @@ namespace BitTorrent
|
|||||||
void setName(const QString &name);
|
void setName(const QString &name);
|
||||||
void setSequentialDownload(bool b);
|
void setSequentialDownload(bool b);
|
||||||
void toggleSequentialDownload();
|
void toggleSequentialDownload();
|
||||||
void setFirstLastPiecePriority(bool enabled);
|
void setFirstLastPiecePriority(bool b);
|
||||||
void toggleFirstLastPiecePriority();
|
void toggleFirstLastPiecePriority();
|
||||||
void pause();
|
void pause();
|
||||||
void resume(bool forced = false);
|
void resume(bool forced = false);
|
||||||
@@ -390,25 +388,24 @@ namespace BitTorrent
|
|||||||
void updateState();
|
void updateState();
|
||||||
void updateTorrentInfo();
|
void updateTorrentInfo();
|
||||||
|
|
||||||
void handleStorageMovedAlert(const libtorrent::storage_moved_alert *p);
|
void handleStorageMovedAlert(libtorrent::storage_moved_alert *p);
|
||||||
void handleStorageMovedFailedAlert(const libtorrent::storage_moved_failed_alert *p);
|
void handleStorageMovedFailedAlert(libtorrent::storage_moved_failed_alert *p);
|
||||||
void handleTrackerReplyAlert(const libtorrent::tracker_reply_alert *p);
|
void handleTrackerReplyAlert(libtorrent::tracker_reply_alert *p);
|
||||||
void handleTrackerWarningAlert(const libtorrent::tracker_warning_alert *p);
|
void handleTrackerWarningAlert(libtorrent::tracker_warning_alert *p);
|
||||||
void handleTrackerErrorAlert(const libtorrent::tracker_error_alert *p);
|
void handleTrackerErrorAlert(libtorrent::tracker_error_alert *p);
|
||||||
void handleTorrentCheckedAlert(const libtorrent::torrent_checked_alert *p);
|
void handleTorrentCheckedAlert(libtorrent::torrent_checked_alert *p);
|
||||||
void handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p);
|
void handleTorrentFinishedAlert(libtorrent::torrent_finished_alert *p);
|
||||||
void handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p);
|
void handleTorrentPausedAlert(libtorrent::torrent_paused_alert *p);
|
||||||
void handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p);
|
void handleTorrentResumedAlert(libtorrent::torrent_resumed_alert *p);
|
||||||
void handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p);
|
void handleSaveResumeDataAlert(libtorrent::save_resume_data_alert *p);
|
||||||
void handleSaveResumeDataFailedAlert(const libtorrent::save_resume_data_failed_alert *p);
|
void handleSaveResumeDataFailedAlert(libtorrent::save_resume_data_failed_alert *p);
|
||||||
void handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p);
|
void handleFastResumeRejectedAlert(libtorrent::fastresume_rejected_alert *p);
|
||||||
void handleFileRenamedAlert(const libtorrent::file_renamed_alert *p);
|
void handleFileRenamedAlert(libtorrent::file_renamed_alert *p);
|
||||||
void handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p);
|
void handleFileRenameFailedAlert(libtorrent::file_rename_failed_alert *p);
|
||||||
void handleFileCompletedAlert(const libtorrent::file_completed_alert *p);
|
void handleFileCompletedAlert(libtorrent::file_completed_alert *p);
|
||||||
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
|
void handleMetadataReceivedAlert(libtorrent::metadata_received_alert *p);
|
||||||
void handleStatsAlert(const libtorrent::stats_alert *p);
|
void handleStatsAlert(libtorrent::stats_alert *p);
|
||||||
|
|
||||||
void resume_impl(bool forced, bool uploadMode);
|
|
||||||
bool isMoveInProgress() const;
|
bool isMoveInProgress() const;
|
||||||
QString nativeActualSavePath() const;
|
QString nativeActualSavePath() const;
|
||||||
|
|
||||||
|
|||||||
@@ -26,20 +26,20 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "torrentinfo.h"
|
#include <QDebug>
|
||||||
|
#include <QString>
|
||||||
|
#include <QList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
#include <libtorrent/error_code.hpp>
|
#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/misc.h"
|
||||||
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "infohash.h"
|
#include "infohash.h"
|
||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
|
#include "torrentinfo.h"
|
||||||
|
|
||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
using namespace BitTorrent;
|
using namespace BitTorrent;
|
||||||
@@ -60,77 +60,23 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
|
|||||||
return *this;
|
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;
|
libt::error_code ec;
|
||||||
TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec)));
|
TorrentInfo info(NativePtr(new libt::torrent_info(Utils::Fs::toNativePath(path).toStdString(), ec)));
|
||||||
if (error) {
|
if (ec) {
|
||||||
if (ec)
|
error = QString::fromUtf8(ec.message().c_str());
|
||||||
*error = QString::fromStdString(ec.message());
|
qDebug("Cannot load .torrent file: %s", qUtf8Printable(error));
|
||||||
else
|
|
||||||
error->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
|
TorrentInfo TorrentInfo::loadFromFile(const QString &path)
|
||||||
{
|
{
|
||||||
if (error)
|
QString error;
|
||||||
error->clear();
|
return loadFromFile(path, error);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentInfo::isValid() const
|
bool TorrentInfo::isValid() const
|
||||||
@@ -315,7 +261,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
|
|||||||
return hashes;
|
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
|
if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
|
||||||
return {};
|
return {};
|
||||||
@@ -353,8 +299,8 @@ void TorrentInfo::renameFile(uint index, const QString &newPath)
|
|||||||
|
|
||||||
int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
||||||
{
|
{
|
||||||
// the check whether the object is valid is not needed here
|
// the check whether the object valid is not needed here
|
||||||
// because if filesCount() returns -1 the loop exits immediately
|
// because filesCount() returns -1 in that case and the loop exits immediately
|
||||||
for (int i = 0; i < filesCount(); ++i)
|
for (int i = 0; i < filesCount(); ++i)
|
||||||
if (fileName == filePath(i))
|
if (fileName == filePath(i))
|
||||||
return i;
|
return i;
|
||||||
@@ -362,29 +308,24 @@ int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentInfo::rootFolder() const
|
bool TorrentInfo::hasRootFolder() const
|
||||||
{
|
{
|
||||||
QString rootFolder;
|
QString testRootFolder;
|
||||||
for (int i = 0; i < filesCount(); ++i) {
|
for (int i = 0; i < filesCount(); ++i) {
|
||||||
const QString filePath = this->filePath(i);
|
const QString filePath = this->filePath(i);
|
||||||
if (QDir::isAbsolutePath(filePath)) continue;
|
if (QDir::isAbsolutePath(filePath)) continue;
|
||||||
|
|
||||||
const auto filePathElements = filePath.splitRef('/');
|
const auto filePathElements = filePath.splitRef('/');
|
||||||
// if at least one file has no root folder, no common root folder exists
|
// 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())
|
if (testRootFolder.isEmpty())
|
||||||
rootFolder = filePathElements.at(0).toString();
|
testRootFolder = filePathElements.at(0).toString();
|
||||||
else if (rootFolder != filePathElements.at(0))
|
else if (testRootFolder != filePathElements.at(0))
|
||||||
return "";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootFolder;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
bool TorrentInfo::hasRootFolder() const
|
|
||||||
{
|
|
||||||
return !rootFolder().isEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentInfo::stripRootFolder()
|
void TorrentInfo::stripRootFolder()
|
||||||
|
|||||||
@@ -29,21 +29,20 @@
|
|||||||
#ifndef BITTORRENT_TORRENTINFO_H
|
#ifndef BITTORRENT_TORRENTINFO_H
|
||||||
#define BITTORRENT_TORRENTINFO_H
|
#define BITTORRENT_TORRENTINFO_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include <libtorrent/torrent_info.hpp>
|
#include <libtorrent/torrent_info.hpp>
|
||||||
#include <libtorrent/version.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QList>
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
#include "base/indexrange.h"
|
#include "base/indexrange.h"
|
||||||
|
|
||||||
class QByteArray;
|
|
||||||
class QDateTime;
|
|
||||||
class QString;
|
class QString;
|
||||||
class QStringList;
|
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
class QDateTime;
|
||||||
|
class QStringList;
|
||||||
|
class QByteArray;
|
||||||
|
template<typename T> class QList;
|
||||||
|
template<typename T> class QVector;
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
@@ -52,8 +51,6 @@ namespace BitTorrent
|
|||||||
|
|
||||||
class TorrentInfo
|
class TorrentInfo
|
||||||
{
|
{
|
||||||
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
#if LIBTORRENT_VERSION_NUM < 10100
|
#if LIBTORRENT_VERSION_NUM < 10100
|
||||||
typedef boost::intrusive_ptr<const libtorrent::torrent_info> NativeConstPtr;
|
typedef boost::intrusive_ptr<const libtorrent::torrent_info> NativeConstPtr;
|
||||||
@@ -66,8 +63,8 @@ namespace BitTorrent
|
|||||||
explicit TorrentInfo(NativeConstPtr nativeInfo = NativeConstPtr());
|
explicit TorrentInfo(NativeConstPtr nativeInfo = NativeConstPtr());
|
||||||
TorrentInfo(const TorrentInfo &other);
|
TorrentInfo(const TorrentInfo &other);
|
||||||
|
|
||||||
static TorrentInfo load(const QByteArray &data, QString *error = nullptr) noexcept;
|
static TorrentInfo loadFromFile(const QString &path, QString &error);
|
||||||
static TorrentInfo loadFromFile(const QString &path, QString *error = nullptr) noexcept;
|
static TorrentInfo loadFromFile(const QString &path);
|
||||||
|
|
||||||
TorrentInfo &operator=(const TorrentInfo &other);
|
TorrentInfo &operator=(const TorrentInfo &other);
|
||||||
|
|
||||||
@@ -104,7 +101,6 @@ namespace BitTorrent
|
|||||||
|
|
||||||
void renameFile(uint index, const QString &newPath);
|
void renameFile(uint index, const QString &newPath);
|
||||||
|
|
||||||
QString rootFolder() const;
|
|
||||||
bool hasRootFolder() const;
|
bool hasRootFolder() const;
|
||||||
void stripRootFolder();
|
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) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
@@ -27,18 +27,15 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "tracker.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <libtorrent/bencode.hpp>
|
#include <libtorrent/bencode.hpp>
|
||||||
#include <libtorrent/entry.hpp>
|
#include <libtorrent/entry.hpp>
|
||||||
|
|
||||||
#include "base/global.h"
|
|
||||||
#include "base/http/server.h"
|
#include "base/http/server.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/utils/bytearray.h"
|
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
#include "tracker.h"
|
||||||
|
|
||||||
// static limits
|
// static limits
|
||||||
static const int MAX_TORRENTS = 100;
|
static const int MAX_TORRENTS = 100;
|
||||||
@@ -77,7 +74,7 @@ libtorrent::entry Peer::toEntry(bool noPeerId) const
|
|||||||
// Tracker
|
// Tracker
|
||||||
|
|
||||||
Tracker::Tracker(QObject *parent)
|
Tracker::Tracker(QObject *parent)
|
||||||
: QObject(parent)
|
: Http::ResponseBuilder(parent)
|
||||||
, m_server(new Http::Server(this, this))
|
, m_server(new Http::Server(this, this))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -133,30 +130,19 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
|
|||||||
|
|
||||||
void Tracker::respondToAnnounceRequest()
|
void Tracker::respondToAnnounceRequest()
|
||||||
{
|
{
|
||||||
QMap<QString, QByteArray> queryParams;
|
const QStringMap &gets = m_request.gets;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackerAnnounceRequest annonceReq;
|
TrackerAnnounceRequest annonceReq;
|
||||||
|
|
||||||
// IP
|
// IP
|
||||||
annonceReq.peer.ip = m_env.clientAddress.toString();
|
annonceReq.peer.ip = m_env.clientAddress.toString();
|
||||||
|
|
||||||
// 1. Get info_hash
|
// 1. Get info_hash
|
||||||
if (!queryParams.contains("info_hash")) {
|
if (!gets.contains("info_hash")) {
|
||||||
qDebug("Tracker: Missing info_hash");
|
qDebug("Tracker: Missing info_hash");
|
||||||
status(101, "Missing info_hash");
|
status(101, "Missing info_hash");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
annonceReq.infoHash = queryParams.value("info_hash");
|
annonceReq.infoHash = gets.value("info_hash");
|
||||||
// info_hash cannot be longer than 20 bytes
|
// info_hash cannot be longer than 20 bytes
|
||||||
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
|
/*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());
|
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
|
// 2. Get peer ID
|
||||||
if (!queryParams.contains("peer_id")) {
|
if (!gets.contains("peer_id")) {
|
||||||
qDebug("Tracker: Missing peer_id");
|
qDebug("Tracker: Missing peer_id");
|
||||||
status(102, "Missing peer_id");
|
status(102, "Missing peer_id");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
annonceReq.peer.peerId = queryParams.value("peer_id");
|
annonceReq.peer.peerId = gets.value("peer_id");
|
||||||
// peer_id cannot be longer than 20 bytes
|
// peer_id cannot be longer than 20 bytes
|
||||||
/*if (annonce_req.peer.peer_id.length() > 20) {
|
/*if (annonce_req.peer.peer_id.length() > 20) {
|
||||||
qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id));
|
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
|
// 3. Get port
|
||||||
if (!queryParams.contains("port")) {
|
if (!gets.contains("port")) {
|
||||||
qDebug("Tracker: Missing port");
|
qDebug("Tracker: Missing port");
|
||||||
status(103, "Missing port");
|
status(103, "Missing port");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
annonceReq.peer.port = queryParams.value("port").toInt(&ok);
|
annonceReq.peer.port = gets.value("port").toInt(&ok);
|
||||||
if (!ok || (annonceReq.peer.port < 0) || (annonceReq.peer.port > 65535)) {
|
if (!ok || annonceReq.peer.port < 1 || annonceReq.peer.port > 65535) {
|
||||||
qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port);
|
qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port);
|
||||||
status(103, "Missing port");
|
status(103, "Missing port");
|
||||||
return;
|
return;
|
||||||
@@ -194,15 +180,15 @@ void Tracker::respondToAnnounceRequest()
|
|||||||
|
|
||||||
// 4. Get event
|
// 4. Get event
|
||||||
annonceReq.event = "";
|
annonceReq.event = "";
|
||||||
if (queryParams.contains("event")) {
|
if (gets.contains("event")) {
|
||||||
annonceReq.event = queryParams.value("event");
|
annonceReq.event = gets.value("event");
|
||||||
qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event));
|
qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Get numwant
|
// 5. Get numwant
|
||||||
annonceReq.numwant = 50;
|
annonceReq.numwant = 50;
|
||||||
if (queryParams.contains("numwant")) {
|
if (gets.contains("numwant")) {
|
||||||
int tmp = queryParams.value("numwant").toInt();
|
int tmp = gets.value("numwant").toInt();
|
||||||
if (tmp > 0) {
|
if (tmp > 0) {
|
||||||
qDebug("Tracker: numwant = %d", tmp);
|
qDebug("Tracker: numwant = %d", tmp);
|
||||||
annonceReq.numwant = tmp;
|
annonceReq.numwant = tmp;
|
||||||
@@ -211,51 +197,37 @@ void Tracker::respondToAnnounceRequest()
|
|||||||
|
|
||||||
// 6. no_peer_id (extension)
|
// 6. no_peer_id (extension)
|
||||||
annonceReq.noPeerId = false;
|
annonceReq.noPeerId = false;
|
||||||
if (queryParams.contains("no_peer_id"))
|
if (gets.contains("no_peer_id"))
|
||||||
annonceReq.noPeerId = true;
|
annonceReq.noPeerId = true;
|
||||||
|
|
||||||
// 7. TODO: support "compact" extension
|
// 7. TODO: support "compact" extension
|
||||||
|
|
||||||
// Done parsing, now let's reply
|
// Done parsing, now let's reply
|
||||||
if (annonceReq.event == "stopped") {
|
if (m_torrents.contains(annonceReq.infoHash)) {
|
||||||
unregisterPeer(annonceReq);
|
if (annonceReq.event == "stopped") {
|
||||||
|
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
|
||||||
|
m_torrents[annonceReq.infoHash].remove(annonceReq.peer.uid());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
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
|
// Unknown torrent
|
||||||
if (m_torrents.size() == MAX_TORRENTS) {
|
if (m_torrents.size() == MAX_TORRENTS) {
|
||||||
// Reached max size, remove a random torrent
|
// Reached max size, remove a random torrent
|
||||||
m_torrents.erase(m_torrents.begin());
|
m_torrents.erase(m_torrents.begin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the user
|
// Register the user
|
||||||
PeerList &peers = m_torrents[annonceReq.infoHash];
|
PeerList peers = m_torrents.value(annonceReq.infoHash);
|
||||||
if (!peers.contains(annonceReq.peer.uid())) {
|
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||||
// Unknown peer
|
// Too many peers, remove a random one
|
||||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
peers.erase(peers.begin());
|
||||||
// Too many peers, remove a random one
|
|
||||||
peers.erase(peers.begin());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
peers[annonceReq.peer.uid()] = annonceReq.peer;
|
peers[annonceReq.peer.uid()] = annonceReq.peer;
|
||||||
}
|
m_torrents[annonceReq.infoHash] = peers;
|
||||||
|
|
||||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &annonceReq)
|
// Reply
|
||||||
{
|
replyWithPeerList(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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
||||||
@@ -263,18 +235,22 @@ void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
|||||||
// Prepare the entry for bencoding
|
// Prepare the entry for bencoding
|
||||||
libtorrent::entry::dictionary_type replyDict;
|
libtorrent::entry::dictionary_type replyDict;
|
||||||
replyDict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
replyDict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
||||||
|
QList<Peer> peers = m_torrents.value(annonceReq.infoHash).values();
|
||||||
libtorrent::entry::list_type peerList;
|
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));
|
peerList.push_back(p.toEntry(annonceReq.noPeerId));
|
||||||
|
}
|
||||||
replyDict["peers"] = libtorrent::entry(peerList);
|
replyDict["peers"] = libtorrent::entry(peerList);
|
||||||
|
libtorrent::entry replyEntry(replyDict);
|
||||||
const libtorrent::entry replyEntry(replyDict);
|
|
||||||
// bencode
|
// bencode
|
||||||
QByteArray reply;
|
std::vector<char> buf;
|
||||||
libtorrent::bencode(std::back_inserter(reply), replyEntry);
|
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());
|
qDebug("Tracker: reply with the following bencoded data:\n %s", reply.constData());
|
||||||
|
|
||||||
// HTTP reply
|
// HTTP reply
|
||||||
print(reply, Http::CONTENT_TYPE_TXT);
|
print(reply, Http::CONTENT_TYPE_TXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
#define BITTORRENT_TRACKER_H
|
#define BITTORRENT_TRACKER_H
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "base/http/irequesthandler.h"
|
#include "base/http/irequesthandler.h"
|
||||||
#include "base/http/responsebuilder.h"
|
#include "base/http/responsebuilder.h"
|
||||||
@@ -52,7 +51,7 @@ namespace BitTorrent
|
|||||||
struct Peer
|
struct Peer
|
||||||
{
|
{
|
||||||
QString ip;
|
QString ip;
|
||||||
QByteArray peerId;
|
QString peerId;
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
bool operator!=(const Peer &other) const;
|
bool operator!=(const Peer &other) const;
|
||||||
@@ -63,7 +62,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
struct TrackerAnnounceRequest
|
struct TrackerAnnounceRequest
|
||||||
{
|
{
|
||||||
QByteArray infoHash;
|
QString infoHash;
|
||||||
QString event;
|
QString event;
|
||||||
int numwant;
|
int numwant;
|
||||||
Peer peer;
|
Peer peer;
|
||||||
@@ -72,11 +71,11 @@ namespace BitTorrent
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef QHash<QString, Peer> PeerList;
|
typedef QHash<QString, Peer> PeerList;
|
||||||
typedef QHash<QByteArray, PeerList> TorrentList;
|
typedef QHash<QString, PeerList> TorrentList;
|
||||||
|
|
||||||
/* Basic Bittorrent tracker implementation in Qt */
|
/* Basic Bittorrent tracker implementation in Qt */
|
||||||
/* Following http://wiki.theory.org/BitTorrent_Tracker_Protocol */
|
/* 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_OBJECT
|
||||||
Q_DISABLE_COPY(Tracker)
|
Q_DISABLE_COPY(Tracker)
|
||||||
@@ -90,8 +89,6 @@ namespace BitTorrent
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void respondToAnnounceRequest();
|
void respondToAnnounceRequest();
|
||||||
void registerPeer(const TrackerAnnounceRequest &annonceReq);
|
|
||||||
void unregisterPeer(const TrackerAnnounceRequest &annonceReq);
|
|
||||||
void replyWithPeerList(const TrackerAnnounceRequest &annonceReq);
|
void replyWithPeerList(const TrackerAnnounceRequest &annonceReq);
|
||||||
|
|
||||||
Http::Server *m_server;
|
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>
|
#include <QtGlobal>
|
||||||
|
#ifndef Q_OS_WIN
|
||||||
|
#include <QSet>
|
||||||
|
#include <iostream>
|
||||||
|
#include <errno.h>
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||||
#include <cstring>
|
|
||||||
#include <sys/mount.h>
|
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <string.h>
|
||||||
|
#elif !defined Q_OS_HAIKU
|
||||||
|
#include <sys/vfs.h>
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/bittorrent/magneturi.h"
|
#include "base/bittorrent/magneturi.h"
|
||||||
#include "base/bittorrent/torrentinfo.h"
|
#include "base/bittorrent/torrentinfo.h"
|
||||||
#include "base/global.h"
|
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/utils/fs.h"
|
#include "filesystemwatcher.h"
|
||||||
|
|
||||||
namespace
|
#ifndef CIFS_MAGIC_NUMBER
|
||||||
{
|
#define CIFS_MAGIC_NUMBER 0xFF534D42
|
||||||
const int WATCH_INTERVAL = 10000; // 10 sec
|
#endif
|
||||||
const int MAX_PARTIAL_RETRIES = 5;
|
|
||||||
}
|
#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)
|
FileSystemWatcher::FileSystemWatcher(QObject *parent)
|
||||||
: QFileSystemWatcher(parent)
|
: QFileSystemWatcher(parent)
|
||||||
{
|
{
|
||||||
connect(this, &QFileSystemWatcher::directoryChanged, this, &FileSystemWatcher::scanLocalFolder);
|
m_filters << "*.torrent" << "*.magnet";
|
||||||
|
connect(this, SIGNAL(directoryChanged(QString)), SLOT(scanLocalFolder(QString)));
|
||||||
m_partialTorrentTimer.setSingleShot(true);
|
}
|
||||||
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
|
|
||||||
|
|
||||||
|
FileSystemWatcher::~FileSystemWatcher()
|
||||||
|
{
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
|
if (m_watchTimer)
|
||||||
|
delete m_watchTimer;
|
||||||
#endif
|
#endif
|
||||||
|
if (m_partialTorrentTimer)
|
||||||
|
delete m_partialTorrentTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList FileSystemWatcher::directories() const
|
QStringList FileSystemWatcher::directories() const
|
||||||
{
|
{
|
||||||
QStringList dirs = QFileSystemWatcher::directories();
|
QStringList dirs;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
if (m_watchTimer) {
|
||||||
dirs << dir.canonicalPath();
|
foreach (const QDir &dir, m_watchedFolders)
|
||||||
|
dirs << dir.canonicalPath();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
dirs << QFileSystemWatcher::directories();
|
||||||
return dirs;
|
return dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemWatcher::addPath(const QString &path)
|
void FileSystemWatcher::addPath(const QString &path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty()) return;
|
|
||||||
|
|
||||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||||
const QDir dir(path);
|
QDir dir(path);
|
||||||
if (!dir.exists()) return;
|
if (!dir.exists()) return;
|
||||||
|
|
||||||
// Check if the path points to a network file system or not
|
// Check if the path points to a network file system or not
|
||||||
if (Utils::Fs::isNetworkFileSystem(path)) {
|
if (isNetworkFileSystem(path)) {
|
||||||
// Network mode
|
// Network mode
|
||||||
qDebug("Network folder detected: %s", qUtf8Printable(path));
|
qDebug("Network folder detected: %s", qUtf8Printable(path));
|
||||||
qDebug("Using file polling mode instead of inotify...");
|
qDebug("Using file polling mode instead of inotify...");
|
||||||
m_watchedFolders << dir;
|
m_watchedFolders << dir;
|
||||||
|
// Set up the watch timer
|
||||||
m_watchTimer.start(WATCH_INTERVAL);
|
if (!m_watchTimer) {
|
||||||
return;
|
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
|
#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)
|
void FileSystemWatcher::removePath(const QString &path)
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
if (m_watchedFolders.removeOne(path)) {
|
QDir dir(path);
|
||||||
if (m_watchedFolders.isEmpty())
|
for (int i = 0; i < m_watchedFolders.count(); ++i) {
|
||||||
m_watchTimer.stop();
|
if (QDir(m_watchedFolders.at(i)) == dir) {
|
||||||
return;
|
m_watchedFolders.removeAt(i);
|
||||||
|
if (m_watchedFolders.isEmpty())
|
||||||
|
delete m_watchTimer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Normal mode
|
// Normal mode
|
||||||
QFileSystemWatcher::removePath(path);
|
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()
|
void FileSystemWatcher::scanNetworkFolders()
|
||||||
{
|
{
|
||||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
#ifndef Q_OS_WIN
|
||||||
processTorrentsInDir(dir);
|
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
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void FileSystemWatcher::processPartialTorrents()
|
void FileSystemWatcher::processPartialTorrents()
|
||||||
{
|
{
|
||||||
QStringList noLongerPartial;
|
QStringList noLongerPartial;
|
||||||
|
|
||||||
// Check which torrents are still partial
|
// Check which torrents are still partial
|
||||||
Dict::removeIf(m_partialTorrents, [&noLongerPartial](const QString &torrentPath, int &value)
|
foreach (const QString &torrentPath, m_partialTorrents.keys()) {
|
||||||
{
|
if (!QFile::exists(torrentPath)) {
|
||||||
if (!QFile::exists(torrentPath))
|
m_partialTorrents.remove(torrentPath);
|
||||||
return true;
|
}
|
||||||
|
else if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) {
|
||||||
if (BitTorrent::TorrentInfo::loadFromFile(torrentPath).isValid()) {
|
|
||||||
noLongerPartial << torrentPath;
|
noLongerPartial << torrentPath;
|
||||||
return true;
|
m_partialTorrents.remove(torrentPath);
|
||||||
}
|
}
|
||||||
|
else if (m_partialTorrents[torrentPath] >= MAX_PARTIAL_RETRIES) {
|
||||||
if (value >= MAX_PARTIAL_RETRIES) {
|
m_partialTorrents.remove(torrentPath);
|
||||||
QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
|
QFile::rename(torrentPath, torrentPath + ".invalid");
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
++value;
|
++m_partialTorrents[torrentPath];
|
||||||
return false;
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Stop the partial timer if necessary
|
// Stop the partial timer if necessary
|
||||||
if (m_partialTorrents.empty()) {
|
if (m_partialTorrents.empty()) {
|
||||||
m_partialTorrentTimer.stop();
|
m_partialTorrentTimer->stop();
|
||||||
|
m_partialTorrentTimer->deleteLater();
|
||||||
qDebug("No longer any partial torrent.");
|
qDebug("No longer any partial torrent.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
|
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
|
||||||
m_partialTorrentTimer.start(WATCH_INTERVAL);
|
m_partialTorrentTimer->start(WATCH_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify of new torrents
|
// Notify of new torrents
|
||||||
@@ -163,23 +178,99 @@ void FileSystemWatcher::processPartialTorrents()
|
|||||||
emit torrentsAdded(noLongerPartial);
|
emit torrentsAdded(noLongerPartial);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemWatcher::processTorrentsInDir(const QDir &dir)
|
void FileSystemWatcher::startPartialTorrentTimer()
|
||||||
{
|
{
|
||||||
QStringList torrents;
|
Q_ASSERT(!m_partialTorrents.isEmpty());
|
||||||
const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files);
|
if (!m_partialTorrentTimer) {
|
||||||
for (const QString &file : files) {
|
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);
|
const QString fileAbsPath = dir.absoluteFilePath(file);
|
||||||
if (file.endsWith(".magnet"))
|
if (fileAbsPath.endsWith(".magnet")) {
|
||||||
torrents << fileAbsPath;
|
torrents << fileAbsPath;
|
||||||
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid())
|
}
|
||||||
|
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) {
|
||||||
torrents << fileAbsPath;
|
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())
|
if (!m_partialTorrents.empty())
|
||||||
emit torrentsAdded(torrents);
|
startPartialTorrentTimer();
|
||||||
|
|
||||||
if (!m_partialTorrents.empty() && !m_partialTorrentTimer.isActive())
|
|
||||||
m_partialTorrentTimer.start(WATCH_INTERVAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
#ifndef FILESYSTEMWATCHER_H
|
||||||
#define FILESYSTEMWATCHER_H
|
#define FILESYSTEMWATCHER_H
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QPointer>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@@ -45,6 +18,7 @@ class FileSystemWatcher : public QFileSystemWatcher
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSystemWatcher(QObject *parent = nullptr);
|
explicit FileSystemWatcher(QObject *parent = nullptr);
|
||||||
|
~FileSystemWatcher();
|
||||||
|
|
||||||
QStringList directories() const;
|
QStringList directories() const;
|
||||||
void addPath(const QString &path);
|
void addPath(const QString &path);
|
||||||
@@ -54,23 +28,25 @@ signals:
|
|||||||
void torrentsAdded(const QStringList &pathList);
|
void torrentsAdded(const QStringList &pathList);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void scanLocalFolder(const QString &path);
|
void scanLocalFolder(QString path);
|
||||||
void processPartialTorrents();
|
|
||||||
#ifndef Q_OS_WIN
|
|
||||||
void scanNetworkFolders();
|
void scanNetworkFolders();
|
||||||
#endif
|
void processPartialTorrents();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void processTorrentsInDir(const QDir &dir);
|
void startPartialTorrentTimer();
|
||||||
|
void addTorrentsFromDir(const QDir &dir, QStringList &torrents);
|
||||||
// Partial torrents
|
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||||
QHash<QString, int> m_partialTorrents;
|
static bool isNetworkFileSystem(QString path);
|
||||||
QTimer m_partialTorrentTimer;
|
#endif
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
QList<QDir> m_watchedFolders;
|
QList<QDir> m_watchedFolders;
|
||||||
QTimer m_watchTimer;
|
QPointer<QTimer> m_watchTimer;
|
||||||
#endif
|
#endif
|
||||||
|
QStringList m_filters;
|
||||||
|
// Partial torrents
|
||||||
|
QHash<QString, int> m_partialTorrents;
|
||||||
|
QPointer<QTimer> m_partialTorrentTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // FILESYSTEMWATCHER_H
|
#endif // FILESYSTEMWATCHER_H
|
||||||
|
|||||||
@@ -26,23 +26,5 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
const char C_TORRENT_FILE_EXTENSION[] = ".torrent";
|
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.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
|
||||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
@@ -33,7 +34,6 @@
|
|||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
|
|
||||||
#include "base/logger.h"
|
|
||||||
#include "irequesthandler.h"
|
#include "irequesthandler.h"
|
||||||
#include "requestparser.h"
|
#include "requestparser.h"
|
||||||
#include "responsegenerator.h"
|
#include "responsegenerator.h"
|
||||||
@@ -47,7 +47,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
|||||||
{
|
{
|
||||||
m_socket->setParent(this);
|
m_socket->setParent(this);
|
||||||
m_idleTimer.start();
|
m_idleTimer.start();
|
||||||
connect(m_socket, &QTcpSocket::readyRead, this, &Connection::read);
|
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection::~Connection()
|
Connection::~Connection()
|
||||||
@@ -58,64 +58,37 @@ Connection::~Connection()
|
|||||||
void Connection::read()
|
void Connection::read()
|
||||||
{
|
{
|
||||||
m_idleTimer.restart();
|
m_idleTimer.restart();
|
||||||
|
|
||||||
m_receivedData.append(m_socket->readAll());
|
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()) {
|
switch (err) {
|
||||||
const RequestParser::ParseResult result = RequestParser::parse(m_receivedData);
|
case RequestParser::IncompleteRequest:
|
||||||
|
// Partial request waiting for the rest
|
||||||
|
break;
|
||||||
|
|
||||||
switch (result.status) {
|
case RequestParser::BadRequest:
|
||||||
case RequestParser::ParseStatus::Incomplete: {
|
sendResponse(Response(400, "Bad Request"));
|
||||||
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
|
m_receivedData.clear();
|
||||||
if (m_receivedData.size() > bufferLimit) {
|
break;
|
||||||
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %ld, IP: %s")
|
|
||||||
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
|
|
||||||
|
|
||||||
Response resp(413, "Payload Too Large");
|
case RequestParser::NoError:
|
||||||
resp.headers[HEADER_CONNECTION] = "close";
|
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
|
||||||
|
|
||||||
sendResponse(resp);
|
Response response = m_requestHandler->processRequest(request, env);
|
||||||
m_socket->close();
|
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
||||||
}
|
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
||||||
}
|
sendResponse(response);
|
||||||
return;
|
m_receivedData.clear();
|
||||||
|
break;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::sendResponse(const Response &response) const
|
void Connection::sendResponse(const Response &response)
|
||||||
{
|
{
|
||||||
m_socket->write(toByteArray(response));
|
m_socket->write(toByteArray(response));
|
||||||
|
m_socket->close(); // TODO: remove when HTTP pipelining is supported
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Connection::hasExpired(const qint64 timeout) const
|
bool Connection::hasExpired(const qint64 timeout) const
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ namespace Http
|
|||||||
Q_DISABLE_COPY(Connection)
|
Q_DISABLE_COPY(Connection)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
||||||
~Connection();
|
~Connection();
|
||||||
|
|
||||||
bool hasExpired(qint64 timeout) const;
|
bool hasExpired(qint64 timeout) const;
|
||||||
@@ -59,7 +61,7 @@ namespace Http
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static bool acceptsGzipEncoding(QString codings);
|
static bool acceptsGzipEncoding(QString codings);
|
||||||
void sendResponse(const Response &response) const;
|
void sendResponse(const Response &response);
|
||||||
|
|
||||||
QTcpSocket *m_socket;
|
QTcpSocket *m_socket;
|
||||||
IRequestHandler *m_requestHandler;
|
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.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
|
||||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
* 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 <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
@@ -31,215 +30,242 @@
|
|||||||
#include "requestparser.h"
|
#include "requestparser.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QRegularExpression>
|
#include <QDir>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
#include "base/utils/bytearray.h"
|
const QByteArray EOL("\r\n");
|
||||||
#include "base/utils/string.h"
|
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 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);
|
return RequestParser(maxContentLength).parseHttpRequest(data, request);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
m_request = Request();
|
||||||
return RequestParser().doParse(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
|
// Parse HTTP request header
|
||||||
{
|
|
||||||
// we don't handle malformed requests which use double `LF` as delimiter
|
|
||||||
const int headerEnd = data.indexOf(EOH);
|
const int headerEnd = data.indexOf(EOH);
|
||||||
if (headerEnd < 0) {
|
if (headerEnd < 0) {
|
||||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||||
return {ParseStatus::Incomplete, Request(), 0};
|
return IncompleteRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString httpHeaders = QString::fromLatin1(data.constData(), headerEnd);
|
if (!parseHttpHeader(data.left(headerEnd))) {
|
||||||
if (!parseStartLines(httpHeaders)) {
|
|
||||||
qWarning() << Q_FUNC_INFO << "header parsing error";
|
qWarning() << Q_FUNC_INFO << "header parsing error";
|
||||||
return {ParseStatus::BadRequest, Request(), 0};
|
return BadRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int headerLength = headerEnd + EOH.length();
|
// Parse HTTP request message
|
||||||
|
if (m_request.headers.contains("content-length")) {
|
||||||
// handle supported methods
|
int contentLength = m_request.headers["content-length"].toInt();
|
||||||
if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD))
|
if (contentLength < 0) {
|
||||||
return {ParseStatus::OK, m_request, headerLength};
|
qWarning() << Q_FUNC_INFO << "bad request: content-length is negative";
|
||||||
if (m_request.method == HEADER_REQUEST_METHOD_POST) {
|
return BadRequest;
|
||||||
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};
|
|
||||||
}
|
}
|
||||||
if (contentLength > MAX_CONTENT_SIZE) {
|
|
||||||
|
if (contentLength > static_cast<int>(m_maxContentLength)) {
|
||||||
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
qWarning() << Q_FUNC_INFO << "bad request: message too long";
|
||||||
return {ParseStatus::BadRequest, Request(), 0};
|
return BadRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentLength > 0) {
|
QByteArray content = data.mid(headerEnd + EOH.length(), contentLength);
|
||||||
const QByteArray httpBodyView = midView(data, headerLength, contentLength);
|
if (content.length() < contentLength) {
|
||||||
if (httpBodyView.length() < contentLength) {
|
qDebug() << Q_FUNC_INFO << "incomplete request";
|
||||||
qDebug() << Q_FUNC_INFO << "incomplete request";
|
return IncompleteRequest;
|
||||||
return {ParseStatus::Incomplete, Request(), 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsePostMessage(httpBodyView)) {
|
|
||||||
qWarning() << Q_FUNC_INFO << "message body parsing error";
|
|
||||||
return {ParseStatus::BadRequest, Request(), 0};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
// qDebug() << Q_FUNC_INFO;
|
||||||
return {ParseStatus::BadRequest, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented"
|
// 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 QRegExp rx("^([A-Z]+)\\s+(\\S+)\\s+HTTP/\\d\\.\\d$");
|
||||||
const QVector<QStringRef> lines = data.splitRef(CRLF, QString::SkipEmptyParts);
|
|
||||||
|
|
||||||
// [rfc7230] 3.2.2. Field Order
|
if (rx.indexIn(line.trimmed()) >= 0) {
|
||||||
QStringList requestLines;
|
m_request.method = rx.cap(1);
|
||||||
for (const auto &line : lines) {
|
|
||||||
if (line.at(0).isSpace() && !requestLines.isEmpty()) {
|
QUrl url = QUrl::fromEncoded(rx.cap(2).toLatin1());
|
||||||
// continuation of previous line
|
m_request.path = url.path(); // Path
|
||||||
requestLines.last() += line.toString();
|
|
||||||
}
|
// Parse GET parameters
|
||||||
else {
|
QListIterator<QPair<QString, QString> > i(QUrlQuery(url).queryItems());
|
||||||
requestLines += line.toString();
|
while (i.hasNext()) {
|
||||||
|
QPair<QString, QString> pair = i.next();
|
||||||
|
m_request.gets[pair.first] = pair.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestLines.isEmpty())
|
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||||
return false;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseRequestLine(const QString &line)
|
bool RequestParser::parseHeaderLine(const QString &line, QPair<QString, QString> &out)
|
||||||
{
|
{
|
||||||
// [rfc7230] 3.1.1. Request Line
|
int i = line.indexOf(QLatin1Char(':'));
|
||||||
|
if (i == -1) {
|
||||||
const QRegularExpression re(QLatin1String("^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"));
|
|
||||||
const QRegularExpressionMatch match = re.match(line);
|
|
||||||
|
|
||||||
if (!match.hasMatch()) {
|
|
||||||
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
qWarning() << Q_FUNC_INFO << "invalid http header:" << line;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request Methods
|
out = qMakePair(line.left(i).trimmed().toLower(), line.mid(i + 1).trimmed());
|
||||||
m_request.method = match.captured(1);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Request Target
|
bool RequestParser::parseHttpHeader(const QByteArray &data)
|
||||||
const QByteArray decodedUrl {QByteArray::fromPercentEncoding(match.captured(2).toLatin1())};
|
{
|
||||||
const int sepPos = decodedUrl.indexOf('?');
|
QString str = QString::fromUtf8(data);
|
||||||
m_request.path = QString::fromUtf8(decodedUrl.constData(), (sepPos == -1 ? decodedUrl.size() : sepPos));
|
QStringList lines = str.trimmed().split(EOL);
|
||||||
if (sepPos >= 0)
|
|
||||||
m_request.query = decodedUrl.mid(sepPos + 1);
|
|
||||||
|
|
||||||
// HTTP-version
|
QStringList headerLines;
|
||||||
m_request.version = match.captured(3);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parsePostMessage(const QByteArray &data)
|
QList<QByteArray> RequestParser::splitMultipartData(const QByteArray &data, const QByteArray &boundary)
|
||||||
{
|
{
|
||||||
// parse POST message-body
|
QList<QByteArray> ret;
|
||||||
const QString contentType = m_request.headers[HEADER_CONTENT_TYPE];
|
QByteArray sep = boundary + EOL;
|
||||||
const QString contentTypeLower = contentType.toLower();
|
const int sepLength = sep.size();
|
||||||
|
|
||||||
// application/x-www-form-urlencoded
|
int start = 0, end = 0;
|
||||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) {
|
if ((end = data.indexOf(sep, start)) >= 0) {
|
||||||
QListIterator<QStringPair> i(QUrlQuery(data).queryItems(QUrl::FullyDecoded));
|
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()) {
|
while (i.hasNext()) {
|
||||||
const QStringPair pair = i.next();
|
QPair<QString, QString> pair = i.next();
|
||||||
m_request.posts[pair.first] = pair.second;
|
m_request.posts[pair.first.toLower()] = pair.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// multipart/form-data
|
// Parse multipart/form data (torrent file)
|
||||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_DATA)) {
|
/**
|
||||||
// [rfc2046] 5.1.1. Common Syntax
|
data has the following format (if boundary is "cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5")
|
||||||
|
|
||||||
// find boundary delimiter
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||||
const QLatin1String boundaryFieldName("boundary=");
|
Content-Disposition: form-data; name=\"Filename\"
|
||||||
const int idx = contentType.indexOf(boundaryFieldName);
|
|
||||||
if (idx < 0) {
|
PB020344.torrent
|
||||||
qWarning() << Q_FUNC_INFO << "Could not find boundary in multipart/form-data header!";
|
--cH2ae0GI3KM7GI3Ij5ae0ei4Ij5Ij5
|
||||||
return false;
|
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();
|
qDebug() << "Boundary is " << boundary;
|
||||||
if (delimiter.isEmpty()) {
|
QList<QByteArray> parts = splitMultipartData(data, boundary);
|
||||||
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
|
qDebug() << parts.size() << "parts in data";
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// split data by "dash-boundary"
|
foreach (const QByteArray& part, parts) {
|
||||||
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) {
|
|
||||||
if (!parseFormData(part))
|
if (!parseFormData(part))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -247,60 +273,71 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
qWarning() << Q_FUNC_INFO << "unknown content type:" << contentType;
|
qWarning() << Q_FUNC_INFO << "unknown content type:" << qPrintable(contentType);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RequestParser::parseFormData(const QByteArray &data)
|
bool RequestParser::parseFormData(const QByteArray &data)
|
||||||
{
|
{
|
||||||
const QList<QByteArray> list = splitToViews(data, EOH, QString::KeepEmptyParts);
|
// Parse form data header
|
||||||
|
const int headerEnd = data.indexOf(EOH);
|
||||||
if (list.size() != 2) {
|
if (headerEnd < 0) {
|
||||||
qWarning() << Q_FUNC_INFO << "multipart/form-data format error";
|
qDebug() << "Invalid form data: \n" << data;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString headers = QString::fromLatin1(list[0]);
|
QString headerStr = QString::fromUtf8(data.left(headerEnd));
|
||||||
const QByteArray payload = viewWithoutEndingWith(list[1], CRLF);
|
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;
|
headers[header.first] = header.second;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick data
|
QStringMap disposition;
|
||||||
const QLatin1String filename("filename");
|
if (!headers.contains("content-disposition")
|
||||||
const QLatin1String name("name");
|
|| !parseHeaderValue(headers["content-disposition"], disposition)
|
||||||
|
|| !disposition.contains("name")) {
|
||||||
if (headersMap.contains(filename)) {
|
qDebug() << "Invalid form data header: \n" << headerStr;
|
||||||
m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload});
|
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 {
|
else {
|
||||||
// malformed
|
m_request.posts[disposition["name"]] = QString::fromUtf8(data.mid(headerEnd + EOH.length()));
|
||||||
qWarning() << Q_FUNC_INFO << "multipart/form-data header error";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
|
||||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
* 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 <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
@@ -38,35 +37,32 @@ namespace Http
|
|||||||
class RequestParser
|
class RequestParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class ParseStatus
|
enum ErrorCode
|
||||||
{
|
{
|
||||||
OK,
|
NoError = 0,
|
||||||
Incomplete,
|
IncompleteRequest,
|
||||||
BadRequest
|
BadRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ParseResult
|
// when result != NoError parsed request is undefined
|
||||||
{
|
// Warning! Header names are converted to lower-case.
|
||||||
// when `status != ParseStatus::OK`, `request` & `frameSize` are undefined
|
static ErrorCode parse(const QByteArray &data, Request &request, uint maxContentLength = 10000000 /* ~10MB */);
|
||||||
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
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RequestParser();
|
RequestParser(uint maxContentLength);
|
||||||
|
|
||||||
ParseResult doParse(const QByteArray &data);
|
ErrorCode parseHttpRequest(const QByteArray &data, Request &request);
|
||||||
bool parseStartLines(const QString &data);
|
|
||||||
bool parseRequestLine(const QString &line);
|
|
||||||
|
|
||||||
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);
|
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;
|
Request m_request;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@
|
|||||||
|
|
||||||
using namespace Http;
|
using namespace Http;
|
||||||
|
|
||||||
|
ResponseBuilder::ResponseBuilder(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void ResponseBuilder::status(uint code, const QString &text)
|
void ResponseBuilder::status(uint code, const QString &text)
|
||||||
{
|
{
|
||||||
m_response.status = ResponseStatus(code, text);
|
m_response.status = ResponseStatus(code, text);
|
||||||
|
|||||||
@@ -29,13 +29,17 @@
|
|||||||
#ifndef HTTP_RESPONSEBUILDER_H
|
#ifndef HTTP_RESPONSEBUILDER_H
|
||||||
#define HTTP_RESPONSEBUILDER_H
|
#define HTTP_RESPONSEBUILDER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
class ResponseBuilder
|
class ResponseBuilder : public QObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
explicit ResponseBuilder(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
protected:
|
||||||
void status(uint code = 200, const QString &text = QLatin1String("OK"));
|
void status(uint code = 200, const QString &text = QLatin1String("OK"));
|
||||||
void header(const QString &name, const QString &value);
|
void header(const QString &name, const QString &value);
|
||||||
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
|
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
|
||||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -38,12 +37,7 @@
|
|||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
const char METHOD_GET[] = "GET";
|
|
||||||
const char METHOD_POST[] = "POST";
|
|
||||||
|
|
||||||
const char HEADER_CACHE_CONTROL[] = "cache-control";
|
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_ENCODING[] = "content-encoding";
|
||||||
const char HEADER_CONTENT_LENGTH[] = "content-length";
|
const char HEADER_CONTENT_LENGTH[] = "content-length";
|
||||||
const char HEADER_CONTENT_SECURITY_POLICY[] = "content-security-policy";
|
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_FRAME_OPTIONS[] = "x-frame-options";
|
||||||
const char HEADER_X_XSS_PROTECTION[] = "x-xss-protection";
|
const char HEADER_X_XSS_PROTECTION[] = "x-xss-protection";
|
||||||
|
|
||||||
const char HEADER_REQUEST_METHOD_GET[] = "GET";
|
const char CONTENT_TYPE_CSS[] = "text/css; charset=UTF-8";
|
||||||
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_GIF[] = "image/gif";
|
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_PNG[] = "image/png";
|
||||||
const char CONTENT_TYPE_TXT[] = "text/plain";
|
const char CONTENT_TYPE_TXT[] = "text/plain; charset=UTF-8";
|
||||||
const char CONTENT_TYPE_FORM_ENCODED[] = "application/x-www-form-urlencoded";
|
const char CONTENT_TYPE_SVG[] = "image/svg+xml";
|
||||||
const char CONTENT_TYPE_FORM_DATA[] = "multipart/form-data";
|
|
||||||
|
|
||||||
// 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'};
|
const char CRLF[] = {0x0D, 0x0A, '\0'};
|
||||||
|
|
||||||
struct Environment
|
struct Environment
|
||||||
@@ -84,18 +75,17 @@ namespace Http
|
|||||||
|
|
||||||
struct UploadedFile
|
struct UploadedFile
|
||||||
{
|
{
|
||||||
QString filename;
|
QString filename; // original filename
|
||||||
QString type; // MIME type
|
QString type; // MIME type
|
||||||
QByteArray data;
|
QByteArray data; // File data
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Request
|
struct Request
|
||||||
{
|
{
|
||||||
QString version;
|
|
||||||
QString method;
|
QString method;
|
||||||
QString path;
|
QString path;
|
||||||
QByteArray query;
|
|
||||||
QStringMap headers;
|
QStringMap headers;
|
||||||
|
QStringMap gets;
|
||||||
QStringMap posts;
|
QStringMap posts;
|
||||||
QVector<UploadedFile> files;
|
QVector<UploadedFile> files;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ void IconProvider::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete 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";
|
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);
|
virtual QString getIconPath(const QString &iconId);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit IconProvider(QObject *parent = nullptr);
|
explicit IconProvider(QObject *parent = 0);
|
||||||
~IconProvider();
|
~IconProvider();
|
||||||
|
|
||||||
static IconProvider *m_instance;
|
static IconProvider *m_instance;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
|
||||||
Logger *Logger::m_instance = nullptr;
|
Logger* Logger::m_instance = 0;
|
||||||
|
|
||||||
Logger::Logger()
|
Logger::Logger()
|
||||||
: m_lock(QReadWriteLock::Recursive)
|
: lock(QReadWriteLock::Recursive)
|
||||||
, m_msgCounter(0)
|
, msgCounter(0)
|
||||||
, m_peerCounter(0)
|
, peerCounter(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,15 +29,15 @@ void Logger::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete m_instance;
|
delete m_instance;
|
||||||
m_instance = nullptr;
|
m_instance = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
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);
|
m_messages.push_back(temp);
|
||||||
|
|
||||||
if (m_messages.size() >= MAX_LOG_MESSAGES)
|
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)
|
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);
|
m_peers.push_back(temp);
|
||||||
|
|
||||||
if (m_peers.size() >= MAX_LOG_MESSAGES)
|
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
|
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();
|
int size = m_messages.size();
|
||||||
|
|
||||||
if ((lastKnownId == -1) || (diff >= 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
|
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();
|
int size = m_peers.size();
|
||||||
|
|
||||||
if ((lastKnownId == -1) || (diff >= size))
|
if ((lastKnownId == -1) || (diff >= size))
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#ifndef LOGGER_H
|
#ifndef LOGGER_H
|
||||||
#define LOGGER_H
|
#define LOGGER_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QReadWriteLock>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QReadWriteLock>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
const int MAX_LOG_MESSAGES = 20000;
|
const int MAX_LOG_MESSAGES = 20000;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ namespace Log
|
|||||||
NORMAL = 0x1,
|
NORMAL = 0x1,
|
||||||
INFO = 0x2,
|
INFO = 0x2,
|
||||||
WARNING = 0x4,
|
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)
|
Q_DECLARE_FLAGS(MsgTypes, MsgType)
|
||||||
|
|
||||||
@@ -63,12 +63,12 @@ private:
|
|||||||
Logger();
|
Logger();
|
||||||
~Logger();
|
~Logger();
|
||||||
|
|
||||||
static Logger *m_instance;
|
static Logger* m_instance;
|
||||||
QVector<Log::Msg> m_messages;
|
QVector<Log::Msg> m_messages;
|
||||||
QVector<Log::Peer> m_peers;
|
QVector<Log::Peer> m_peers;
|
||||||
mutable QReadWriteLock m_lock;
|
mutable QReadWriteLock lock;
|
||||||
int m_msgCounter;
|
int msgCounter;
|
||||||
int m_peerCounter;
|
int peerCounter;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ DNSUpdater::DNSUpdater(QObject *parent)
|
|||||||
|
|
||||||
// Start IP checking timer
|
// Start IP checking timer
|
||||||
m_ipCheckTimer.setInterval(IP_CHECK_INTERVAL_MS);
|
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();
|
m_ipCheckTimer.start();
|
||||||
|
|
||||||
// Check lastUpdate to avoid flooding
|
// Check lastUpdate to avoid flooding
|
||||||
@@ -77,9 +77,8 @@ void DNSUpdater::checkPublicIP()
|
|||||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||||
"http://checkip.dyndns.org", false, 0, false,
|
"http://checkip.dyndns.org", false, 0, false,
|
||||||
"qBittorrent/" QBT_VERSION_2);
|
"qBittorrent/" QBT_VERSION_2);
|
||||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipRequestFinished(QString, QByteArray)));
|
||||||
, this, &DNSUpdater::ipRequestFinished);
|
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipRequestFailed(QString, QString)));
|
||||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipRequestFailed);
|
|
||||||
|
|
||||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||||
}
|
}
|
||||||
@@ -125,9 +124,8 @@ void DNSUpdater::updateDNSService()
|
|||||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||||
getUpdateUrl(), false, 0, false,
|
getUpdateUrl(), false, 0, false,
|
||||||
"qBittorrent/" QBT_VERSION_2);
|
"qBittorrent/" QBT_VERSION_2);
|
||||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(ipUpdateFinished(QString, QByteArray)));
|
||||||
, this, &DNSUpdater::ipUpdateFinished);
|
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(ipUpdateFailed(QString, QString)));
|
||||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipUpdateFailed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DNSUpdater::getUpdateUrl() const
|
QString DNSUpdater::getUpdateUrl() const
|
||||||
@@ -198,7 +196,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
|||||||
return;
|
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_ipCheckTimer.stop();
|
||||||
m_lastIP.clear();
|
m_lastIP.clear();
|
||||||
if (code == "nohost") {
|
if (code == "nohost") {
|
||||||
|
|||||||
@@ -121,15 +121,15 @@ void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
|
|||||||
// Total number of bytes is available
|
// Total number of bytes is available
|
||||||
if (bytesTotal > m_sizeLimit) {
|
if (bytesTotal > m_sizeLimit) {
|
||||||
m_reply->abort();
|
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 {
|
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) {
|
else if (bytesReceived > m_sizeLimit) {
|
||||||
m_reply->abort();
|
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);
|
m_reply->setParent(this);
|
||||||
if (m_sizeLimit > 0)
|
if (m_sizeLimit > 0)
|
||||||
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(checkDownloadSize(qint64, qint64)));
|
||||||
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
|
connect(m_reply, SIGNAL(finished()), this, SLOT(processFinishedDownload()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
|
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
|
namespace
|
||||||
{
|
{
|
||||||
class NetworkCookieJar : public QNetworkCookieJar
|
class NetworkCookieJar: public QNetworkCookieJar
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NetworkCookieJar(QObject *parent = nullptr)
|
explicit NetworkCookieJar(QObject *parent = 0)
|
||||||
: QNetworkCookieJar(parent)
|
: QNetworkCookieJar(parent)
|
||||||
{
|
{
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
@@ -107,13 +107,13 @@ namespace
|
|||||||
|
|
||||||
using namespace Net;
|
using namespace Net;
|
||||||
|
|
||||||
DownloadManager *DownloadManager::m_instance = nullptr;
|
DownloadManager *DownloadManager::m_instance = 0;
|
||||||
|
|
||||||
DownloadManager::DownloadManager(QObject *parent)
|
DownloadManager::DownloadManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
#ifndef QT_NO_OPENSSL
|
#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
|
#endif
|
||||||
m_networkManager.setCookieJar(new NetworkCookieJar(this));
|
m_networkManager.setCookieJar(new NetworkCookieJar(this));
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ void DownloadManager::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete m_instance;
|
delete m_instance;
|
||||||
m_instance = nullptr;
|
m_instance = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace Net
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DownloadManager(QObject *parent = nullptr);
|
explicit DownloadManager(QObject *parent = 0);
|
||||||
|
|
||||||
void applyProxySettings();
|
void applyProxySettings();
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ GeoIPManager::GeoIPManager()
|
|||||||
, m_geoIPDatabase(nullptr)
|
, m_geoIPDatabase(nullptr)
|
||||||
{
|
{
|
||||||
configure();
|
configure();
|
||||||
connect(Preferences::instance(), &Preferences::changed, this, &GeoIPManager::configure);
|
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
|
||||||
}
|
}
|
||||||
|
|
||||||
GeoIPManager::~GeoIPManager()
|
GeoIPManager::~GeoIPManager()
|
||||||
@@ -96,14 +96,15 @@ void GeoIPManager::loadDatabase()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString filepath = Utils::Fs::expandPathAbs(
|
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;
|
QString error;
|
||||||
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
|
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
|
||||||
if (m_geoIPDatabase)
|
if (m_geoIPDatabase)
|
||||||
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
||||||
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
|
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
|
||||||
Log::INFO);
|
Log::INFO);
|
||||||
else
|
else
|
||||||
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
|
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()
|
void GeoIPManager::downloadDatabaseFile()
|
||||||
{
|
{
|
||||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
|
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
|
||||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(downloadFinished(QString, QByteArray)));
|
||||||
, this, &GeoIPManager::downloadFinished);
|
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(downloadFailed(QString, QString)));
|
||||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &GeoIPManager::downloadFailed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
|
QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
|
||||||
@@ -432,13 +432,13 @@ void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
|
|||||||
delete m_geoIPDatabase;
|
delete m_geoIPDatabase;
|
||||||
m_geoIPDatabase = geoIPDatabase;
|
m_geoIPDatabase = geoIPDatabase;
|
||||||
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
|
||||||
.arg(m_geoIPDatabase->type(), m_geoIPDatabase->buildEpoch().toString()),
|
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
|
||||||
Log::INFO);
|
Log::INFO);
|
||||||
QString targetPath = Utils::Fs::expandPathAbs(
|
QString targetPath = Utils::Fs::expandPathAbs(
|
||||||
specialFolderLocation(SpecialFolder::Data) + GEOIP_FOLDER);
|
specialFolderLocation(SpecialFolder::Data) + GEOIP_FOLDER);
|
||||||
if (!QDir(targetPath).exists())
|
if (!QDir(targetPath).exists())
|
||||||
QDir().mkpath(targetPath);
|
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)) {
|
if (!targetFile.open(QFile::WriteOnly) || (targetFile.write(data) == -1)) {
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
|
tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/settingsstorage.h"
|
#include "base/settingsstorage.h"
|
||||||
|
|
||||||
static const QString KEY_ENABLED = QStringLiteral("Network/PortForwardingEnabled");
|
static const QString KEY_ENABLED = QLatin1String("Network/PortForwardingEnabled");
|
||||||
|
|
||||||
namespace libt = libtorrent;
|
namespace libt = libtorrent;
|
||||||
using namespace Net;
|
using namespace Net;
|
||||||
@@ -121,10 +121,8 @@ void PortForwarder::start()
|
|||||||
settingsPack.set_bool(libt::settings_pack::enable_natpmp, true);
|
settingsPack.set_bool(libt::settings_pack::enable_natpmp, true);
|
||||||
m_provider->apply_settings(settingsPack);
|
m_provider->apply_settings(settingsPack);
|
||||||
#endif
|
#endif
|
||||||
for (auto i = m_mappedPorts.begin(); i != m_mappedPorts.end(); ++i) {
|
foreach (quint16 port, m_mappedPorts.keys())
|
||||||
// quint16 port = i.key();
|
m_mappedPorts[port] = m_provider->add_port_mapping(libt::session::tcp, port, port);
|
||||||
i.value() = m_provider->add_port_mapping(libt::session::tcp, i.key(), i.key());
|
|
||||||
}
|
|
||||||
m_active = true;
|
m_active = true;
|
||||||
Logger::instance()->addMessage(tr("UPnP / NAT-PMP support [ON]"), Log::INFO);
|
Logger::instance()->addMessage(tr("UPnP / NAT-PMP support [ON]"), Log::INFO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,23 +26,25 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QVariant>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QVariant>
|
#include <QDateTime>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
#include "base/types.h"
|
#include "base/types.h"
|
||||||
#include "geoipdatabase.h"
|
#include "geoipdatabase.h"
|
||||||
|
|
||||||
namespace
|
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 qint32 MAX_FILE_SIZE = 67108864; // 64MB
|
||||||
const char DB_TYPE[] = "GeoLite2-Country";
|
const char DB_TYPE[] = "GeoLite2-Country";
|
||||||
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
|
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
|
||||||
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
|
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
|
enum class DataType
|
||||||
{
|
{
|
||||||
@@ -89,7 +91,7 @@ GeoIPDatabase::GeoIPDatabase(quint32 size)
|
|||||||
|
|
||||||
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||||
{
|
{
|
||||||
GeoIPDatabase *db = nullptr;
|
GeoIPDatabase *db = 0;
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (file.size() > MAX_FILE_SIZE) {
|
if (file.size() > MAX_FILE_SIZE) {
|
||||||
error = tr("Unsupported database file size.");
|
error = tr("Unsupported database file size.");
|
||||||
@@ -120,7 +122,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
|||||||
|
|
||||||
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||||
{
|
{
|
||||||
GeoIPDatabase *db = nullptr;
|
GeoIPDatabase *db = 0;
|
||||||
if (data.size() > MAX_FILE_SIZE) {
|
if (data.size() > MAX_FILE_SIZE) {
|
||||||
error = tr("Unsupported database file size.");
|
error = tr("Unsupported database file size.");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -446,12 +448,8 @@ bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor
|
|||||||
|
|
||||||
void GeoIPDatabase::fromBigEndian(uchar *buf, quint32 len) const
|
void GeoIPDatabase::fromBigEndian(uchar *buf, quint32 len) const
|
||||||
{
|
{
|
||||||
#if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN)
|
if (__IS_LITTLE_ENDIAN__)
|
||||||
std::reverse(buf, buf + len);
|
std::reverse(buf, buf + len);
|
||||||
#else
|
|
||||||
Q_UNUSED(buf);
|
|
||||||
Q_UNUSED(len);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant GeoIPDatabase::readMapValue(quint32 &offset, quint32 count) const
|
QVariant GeoIPDatabase::readMapValue(quint32 &offset, quint32 count) const
|
||||||
|
|||||||
@@ -29,13 +29,13 @@
|
|||||||
#ifndef GEOIPDATABASE_H
|
#ifndef GEOIPDATABASE_H
|
||||||
#define GEOIPDATABASE_H
|
#define GEOIPDATABASE_H
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
class QByteArray;
|
|
||||||
class QDateTime;
|
|
||||||
class QHostAddress;
|
class QHostAddress;
|
||||||
class QString;
|
class QString;
|
||||||
|
class QByteArray;
|
||||||
|
class QDateTime;
|
||||||
|
|
||||||
struct DataFieldDescriptor;
|
struct DataFieldDescriptor;
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "proxyconfigurationmanager.h"
|
#include "proxyconfigurationmanager.h"
|
||||||
|
|
||||||
#include "base/settingsstorage.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_ONLY_FOR_TORRENTS = SETTINGS_KEY("OnlyForTorrents");
|
||||||
const QString KEY_TYPE = SETTINGS_KEY("Type");
|
const QString KEY_TYPE = SETTINGS_KEY("Type");
|
||||||
const QString KEY_IP = SETTINGS_KEY("IP");
|
const QString KEY_IP = SETTINGS_KEY("IP");
|
||||||
@@ -81,7 +80,7 @@ void ProxyConfigurationManager::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete m_instance;
|
delete m_instance;
|
||||||
m_instance = nullptr;
|
m_instance = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,18 +135,18 @@ void ProxyConfigurationManager::configureProxy()
|
|||||||
if (!m_isProxyOnlyForTorrents) {
|
if (!m_isProxyOnlyForTorrents) {
|
||||||
switch (m_config.type) {
|
switch (m_config.type) {
|
||||||
case ProxyType::HTTP_PW:
|
case ProxyType::HTTP_PW:
|
||||||
proxyStrHTTP = QString("http://%1:%2@%3:%4").arg(m_config.username
|
proxyStrHTTP = QString("http://%1:%2@%3:%4").arg(m_config.username)
|
||||||
, m_config.password, m_config.ip, QString::number(m_config.port));
|
.arg(m_config.password).arg(m_config.ip).arg(m_config.port);
|
||||||
break;
|
break;
|
||||||
case ProxyType::HTTP:
|
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;
|
break;
|
||||||
case ProxyType::SOCKS5:
|
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;
|
break;
|
||||||
case ProxyType::SOCKS5_PW:
|
case ProxyType::SOCKS5_PW:
|
||||||
proxyStrSOCK = QString("%1:%2@%3:%4").arg(m_config.username
|
proxyStrSOCK = QString("%1:%2@%3:%4").arg(m_config.username)
|
||||||
, m_config.password, m_config.ip, QString::number(m_config.port));
|
.arg(m_config.password).arg(m_config.ip).arg(m_config.port);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
qDebug("Disabling HTTP communications proxy");
|
qDebug("Disabling HTTP communications proxy");
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Net
|
|||||||
QString password;
|
QString password;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProxyConfigurationManager : public QObject
|
class ProxyConfigurationManager: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(ProxyConfigurationManager)
|
Q_DISABLE_COPY(ProxyConfigurationManager)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* 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 <QDebug>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <boost/version.hpp>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
|
#include "reverseresolution.h"
|
||||||
|
|
||||||
const int CACHE_SIZE = 500;
|
const int CACHE_SIZE = 500;
|
||||||
|
|
||||||
using namespace Net;
|
using namespace Net;
|
||||||
|
|
||||||
static inline bool isUsefulHostName(const QString &hostname, const QString &ip)
|
static inline bool isUsefulHostName(const QString &hostname, const QString &ip)
|
||||||
{
|
{
|
||||||
return (!hostname.isEmpty() && (hostname != ip));
|
return (!hostname.isEmpty() && hostname != ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReverseResolution::ReverseResolution(QObject *parent)
|
ReverseResolution::ReverseResolution(QObject *parent)
|
||||||
@@ -65,11 +67,7 @@ void ReverseResolution::resolve(const QString &ip)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Actually resolve the ip
|
// 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);
|
m_lookups.insert(QHostInfo::lookupHost(ip, this, SLOT(hostResolved(QHostInfo))), ip);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NET_REVERSERESOLUTION_H
|
#ifndef NET_REVERSERESOLUTION_H
|
||||||
@@ -32,8 +34,10 @@
|
|||||||
#include <QCache>
|
#include <QCache>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
class QHostInfo;
|
class QHostInfo;
|
||||||
class QString;
|
class QString;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
namespace Net
|
namespace Net
|
||||||
{
|
{
|
||||||
@@ -43,7 +47,7 @@ namespace Net
|
|||||||
Q_DISABLE_COPY(ReverseResolution)
|
Q_DISABLE_COPY(ReverseResolution)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ReverseResolution(QObject *parent = nullptr);
|
explicit ReverseResolution(QObject *parent = 0);
|
||||||
~ReverseResolution();
|
~ReverseResolution();
|
||||||
|
|
||||||
void resolve(const QString &ip);
|
void resolve(const QString &ip);
|
||||||
|
|||||||
@@ -111,10 +111,9 @@ Smtp::Smtp(QObject *parent)
|
|||||||
m_socket = new QTcpSocket(this);
|
m_socket = new QTcpSocket(this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(m_socket, &QIODevice::readyRead, this, &Smtp::readyRead);
|
connect(m_socket, SIGNAL(readyRead()), SLOT(readyRead()));
|
||||||
connect(m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater);
|
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
||||||
connect(m_socket, static_cast<void (QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error)
|
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(error(QAbstractSocket::SocketError)));
|
||||||
, this, &Smtp::error);
|
|
||||||
|
|
||||||
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
|
// Test hmacMD5 function (http://www.faqs.org/rfcs/rfc2202.html)
|
||||||
Q_ASSERT(hmacMD5("Jefe", "what do ya want for nothing?").toHex()
|
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)
|
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
|
||||||
{
|
{
|
||||||
const Preferences *const pref = Preferences::instance();
|
const Preferences* const pref = Preferences::instance();
|
||||||
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
|
QTextCodec* latin1 = QTextCodec::codecForName("latin1");
|
||||||
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
||||||
+ encodeMimeHeader("From", from, latin1)
|
+ encodeMimeHeader("From", from, latin1)
|
||||||
+ encodeMimeHeader("Subject", subject, 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"
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
+ "\r\n";
|
+ "\r\n";
|
||||||
// Encode the body in base64
|
// Encode the body in base64
|
||||||
QString crlfBody = body;
|
QString crlf_body = body;
|
||||||
QByteArray b = crlfBody.replace("\n", "\r\n").toUtf8().toBase64();
|
QByteArray b = crlf_body.replace("\n", "\r\n").toUtf8().toBase64();
|
||||||
int ct = b.length();
|
int ct = b.length();
|
||||||
for (int i = 0; i < ct; i += 78)
|
for (int i = 0; i < ct; i += 78)
|
||||||
m_message += b.mid(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_socket->connectToHost(pref->getMailNotificationSMTP(), DEFAULT_PORT);
|
||||||
m_useSsl = false;
|
m_useSsl = false;
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +183,7 @@ void Smtp::readyRead()
|
|||||||
QByteArray code = line.left(3);
|
QByteArray code = line.left(3);
|
||||||
|
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case Init:
|
case Init: {
|
||||||
if (code[0] == '2') {
|
if (code[0] == '2') {
|
||||||
// The server may send a multiline greeting/INIT/220 response.
|
// The server may send a multiline greeting/INIT/220 response.
|
||||||
// We wait until it finishes.
|
// We wait until it finishes.
|
||||||
@@ -198,6 +197,7 @@ void Smtp::readyRead()
|
|||||||
m_state = Close;
|
m_state = Close;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case EhloSent:
|
case EhloSent:
|
||||||
case HeloSent:
|
case HeloSent:
|
||||||
case EhloGreetReceived:
|
case EhloGreetReceived:
|
||||||
@@ -447,7 +447,7 @@ void Smtp::startTLS()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Smtp::authCramMD5(const QByteArray &challenge)
|
void Smtp::authCramMD5(const QByteArray& challenge)
|
||||||
{
|
{
|
||||||
if (m_state != AuthRequestSent) {
|
if (m_state != AuthRequestSent) {
|
||||||
m_socket->write("auth cram-md5\r\n");
|
m_socket->write("auth cram-md5\r\n");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt4 and libtorrent.
|
||||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2014 sledgehammer999
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
|
* Contact : hammered999@gmail.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
#include "utils/fs.h"
|
#include "utils/fs.h"
|
||||||
#include "utils/misc.h"
|
#include "utils/misc.h"
|
||||||
|
|
||||||
Preferences *Preferences::m_instance = nullptr;
|
Preferences *Preferences::m_instance = 0;
|
||||||
|
|
||||||
Preferences::Preferences() = default;
|
Preferences::Preferences() = default;
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ void Preferences::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete 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);
|
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));
|
setValue("Preferences/Scheduler/days", static_cast<int>(days));
|
||||||
}
|
}
|
||||||
@@ -606,26 +609,6 @@ void Preferences::setWebUiHttpsKey(const QByteArray &data)
|
|||||||
setValue("Preferences/WebUI/HTTPS/Key", 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
|
bool Preferences::isDynDNSEnabled() const
|
||||||
{
|
{
|
||||||
return value("Preferences/DynDNS/Enabled", false).toBool();
|
return value("Preferences/DynDNS/Enabled", false).toBool();
|
||||||
@@ -687,12 +670,12 @@ QString Preferences::getUILockPasswordMD5() const
|
|||||||
return value("Locking/password").toString();
|
return value("Locking/password").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Preferences::setUILockPassword(const QString &clearPassword)
|
void Preferences::setUILockPassword(const QString &clear_password)
|
||||||
{
|
{
|
||||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||||
md5.addData(clearPassword.toLocal8Bit());
|
md5.addData(clear_password.toLocal8Bit());
|
||||||
QString md5Password = md5.result().toHex();
|
QString md5_password = md5.result().toHex();
|
||||||
setValue("Locking/password", md5Password);
|
setValue("Locking/password", md5_password);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Preferences::isUILocked() const
|
bool Preferences::isUILocked() const
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt4 and libtorrent.
|
||||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2014 sledgehammer999
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
|
* Contact : hammered999@gmail.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef PREFERENCES_H
|
#ifndef PREFERENCES_H
|
||||||
@@ -44,7 +47,7 @@
|
|||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
enum SchedulerDays
|
enum scheduler_days
|
||||||
{
|
{
|
||||||
EVERY_DAY,
|
EVERY_DAY,
|
||||||
WEEK_DAYS,
|
WEEK_DAYS,
|
||||||
@@ -80,7 +83,7 @@ namespace DNS
|
|||||||
|
|
||||||
class SettingsStorage;
|
class SettingsStorage;
|
||||||
|
|
||||||
class Preferences : public QObject
|
class Preferences: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Preferences)
|
Q_DISABLE_COPY(Preferences)
|
||||||
@@ -163,8 +166,8 @@ public:
|
|||||||
void setSchedulerStartTime(const QTime &time);
|
void setSchedulerStartTime(const QTime &time);
|
||||||
QTime getSchedulerEndTime() const;
|
QTime getSchedulerEndTime() const;
|
||||||
void setSchedulerEndTime(const QTime &time);
|
void setSchedulerEndTime(const QTime &time);
|
||||||
SchedulerDays getSchedulerDays() const;
|
scheduler_days getSchedulerDays() const;
|
||||||
void setSchedulerDays(SchedulerDays days);
|
void setSchedulerDays(scheduler_days days);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
bool isSearchEnabled() const;
|
bool isSearchEnabled() const;
|
||||||
@@ -201,10 +204,6 @@ public:
|
|||||||
void setWebUiHttpsCertificate(const QByteArray &data);
|
void setWebUiHttpsCertificate(const QByteArray &data);
|
||||||
QByteArray getWebUiHttpsKey() const;
|
QByteArray getWebUiHttpsKey() const;
|
||||||
void setWebUiHttpsKey(const QByteArray &data);
|
void setWebUiHttpsKey(const QByteArray &data);
|
||||||
bool isAltWebUiEnabled() const;
|
|
||||||
void setAltWebUiEnabled(bool enabled);
|
|
||||||
QString getWebUiRootFolder() const;
|
|
||||||
void setWebUiRootFolder(const QString &path);
|
|
||||||
|
|
||||||
// Dynamic DNS
|
// Dynamic DNS
|
||||||
bool isDynDNSEnabled() const;
|
bool isDynDNSEnabled() const;
|
||||||
@@ -219,7 +218,7 @@ public:
|
|||||||
void setDynDNSPassword(const QString &password);
|
void setDynDNSPassword(const QString &password);
|
||||||
|
|
||||||
// Advanced settings
|
// Advanced settings
|
||||||
void setUILockPassword(const QString &clearPassword);
|
void setUILockPassword(const QString &clear_password);
|
||||||
void clearUILockPassword();
|
void clearUILockPassword();
|
||||||
QString getUILockPasswordMD5() const;
|
QString getUILockPasswordMD5() const;
|
||||||
bool isUILocked() const;
|
bool isUILocked() const;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
* modify file(s), you may extend this exception to your version of the file(s),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef QBT_PROFILE_P_H
|
#ifndef QBT_PROFILE_P_H
|
||||||
@@ -32,7 +33,6 @@
|
|||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "base/profile.h"
|
#include "base/profile.h"
|
||||||
|
|
||||||
namespace Private
|
namespace Private
|
||||||
@@ -63,7 +63,7 @@ namespace Private
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Default implementation. Takes paths from system
|
/// Default implementation. Takes paths from system
|
||||||
class DefaultProfile : public Profile
|
class DefaultProfile: public Profile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DefaultProfile(const QString &configurationName);
|
DefaultProfile(const QString &configurationName);
|
||||||
@@ -86,7 +86,7 @@ namespace Private
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Custom tree: creates directories under the specified root directory
|
/// Custom tree: creates directories under the specified root directory
|
||||||
class CustomProfile : public Profile
|
class CustomProfile: public Profile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CustomProfile(const QString &rootPath, const QString &configurationName);
|
CustomProfile(const QString &rootPath, const QString &configurationName);
|
||||||
@@ -114,14 +114,14 @@ namespace Private
|
|||||||
virtual ~PathConverter() = default;
|
virtual ~PathConverter() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoConvertConverter : public PathConverter
|
class NoConvertConverter: public PathConverter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QString toPortablePath(const QString &path) const override;
|
QString toPortablePath(const QString &path) const override;
|
||||||
QString fromPortablePath(const QString &portablePath) const override;
|
QString fromPortablePath(const QString &portablePath) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Converter : public PathConverter
|
class Converter: public PathConverter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Converter(const QString &basePath);
|
Converter(const QString &basePath);
|
||||||
@@ -132,5 +132,4 @@ namespace Private
|
|||||||
QDir m_baseDir;
|
QDir m_baseDir;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // QBT_PROFILE_P_H
|
#endif // QBT_PROFILE_P_H
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
* modify file(s), you may extend this exception to your version of the file(s),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "profile.h"
|
#include "profile.h"
|
||||||
@@ -42,6 +43,7 @@ Profile::Profile(Private::Profile *impl, Private::PathConverter *pathConverter)
|
|||||||
ensureDirectoryExists(SpecialFolder::Cache);
|
ensureDirectoryExists(SpecialFolder::Cache);
|
||||||
ensureDirectoryExists(SpecialFolder::Config);
|
ensureDirectoryExists(SpecialFolder::Config);
|
||||||
ensureDirectoryExists(SpecialFolder::Data);
|
ensureDirectoryExists(SpecialFolder::Data);
|
||||||
|
ensureDirectoryExists(SpecialFolder::Downloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
// to generate correct call to ProfilePrivate::~ProfileImpl()
|
// to generate correct call to ProfilePrivate::~ProfileImpl()
|
||||||
|
|||||||
@@ -33,9 +33,9 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
|
||||||
|
|||||||
@@ -25,339 +25,24 @@
|
|||||||
* modify file(s), you may extend this exception to your version of the file(s),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rss_parser.h"
|
#include "rss_parser.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QGlobalStatic>
|
#include <QDateTime>
|
||||||
#include <QHash>
|
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QXmlStreamEntityResolver>
|
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include "../rss_article.h"
|
#include "../rss_article.h"
|
||||||
|
|
||||||
namespace
|
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] = {
|
const char shortDay[][4] = {
|
||||||
"Mon", "Tue", "Wed",
|
"Mon", "Tue", "Wed",
|
||||||
"Thu", "Fri", "Sat",
|
"Thu", "Fri", "Sat",
|
||||||
@@ -543,8 +228,6 @@ void Parser::parse(const QByteArray &feedData)
|
|||||||
void Parser::parse_impl(const QByteArray &feedData)
|
void Parser::parse_impl(const QByteArray &feedData)
|
||||||
{
|
{
|
||||||
QXmlStreamReader xml(feedData);
|
QXmlStreamReader xml(feedData);
|
||||||
XmlStreamEntityResolver resolver;
|
|
||||||
xml.setEntityResolver(&resolver);
|
|
||||||
bool foundChannel = false;
|
bool foundChannel = false;
|
||||||
|
|
||||||
while (xml.readNextStartElement()) {
|
while (xml.readNextStartElement()) {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
* modify file(s), you may extend this exception to your version of the file(s),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -48,7 +50,7 @@ namespace RSS
|
|||||||
QList<QVariantHash> articles;
|
QList<QVariantHash> articles;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser : public QObject
|
class Parser: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|||||||
@@ -38,19 +38,6 @@
|
|||||||
|
|
||||||
using namespace RSS;
|
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::KeyId(QStringLiteral("id"));
|
||||||
const QString Article::KeyDate(QStringLiteral("date"));
|
const QString Article::KeyDate(QStringLiteral("date"));
|
||||||
const QString Article::KeyTitle(QStringLiteral("title"));
|
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_isRead(varHash.value(KeyIsRead, false).toBool())
|
||||||
, m_data(varHash)
|
, 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 item does not have a guid, fall back to some other identifier
|
||||||
if (m_guid.isEmpty())
|
if (m_guid.isEmpty())
|
||||||
m_guid = varHash.value(KeyTorrentURL).toString();
|
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::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
|
QString Article::guid() const
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace RSS
|
|||||||
{
|
{
|
||||||
class Feed;
|
class Feed;
|
||||||
|
|
||||||
class Article : public QObject
|
class Article: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Article)
|
Q_DISABLE_COPY(Article)
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
#include "../bittorrent/magneturi.h"
|
#include "../bittorrent/magneturi.h"
|
||||||
#include "../bittorrent/session.h"
|
#include "../bittorrent/session.h"
|
||||||
#include "../asyncfilestorage.h"
|
#include "../asyncfilestorage.h"
|
||||||
#include "../global.h"
|
|
||||||
#include "../logger.h"
|
#include "../logger.h"
|
||||||
#include "../profile.h"
|
#include "../profile.h"
|
||||||
#include "../settingsstorage.h"
|
#include "../settingsstorage.h"
|
||||||
@@ -65,7 +64,6 @@ const QString ConfFolderName(QStringLiteral("rss"));
|
|||||||
const QString RulesFileName(QStringLiteral("download_rules.json"));
|
const QString RulesFileName(QStringLiteral("download_rules.json"));
|
||||||
|
|
||||||
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
||||||
const QString SettingsKey_SmartEpisodeFilter(QStringLiteral("RSS/AutoDownloader/SmartEpisodeFilter"));
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -97,11 +95,6 @@ using namespace RSS;
|
|||||||
|
|
||||||
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
QPointer<AutoDownloader> AutoDownloader::m_instance = nullptr;
|
||||||
|
|
||||||
QString computeSmartFilterRegex(const QStringList &filters)
|
|
||||||
{
|
|
||||||
return QString("(?:_|\\b)(?:%1)(?:_|\\b)").arg(filters.join(QString(")|(?:")));
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoDownloader::AutoDownloader()
|
AutoDownloader::AutoDownloader()
|
||||||
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false).toBool())
|
: m_processingEnabled(SettingsStorage::instance()->loadValue(SettingsKey_ProcessingEnabled, false).toBool())
|
||||||
, m_processingTimer(new QTimer(this))
|
, m_processingTimer(new QTimer(this))
|
||||||
@@ -120,7 +113,7 @@ AutoDownloader::AutoDownloader()
|
|||||||
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
connect(m_fileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't save RSS AutoDownloader data in %1. Error: %2")
|
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();
|
m_ioThread->start();
|
||||||
@@ -130,13 +123,6 @@ AutoDownloader::AutoDownloader()
|
|||||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed
|
||||||
, this, &AutoDownloader::handleTorrentDownloadFailed);
|
, this, &AutoDownloader::handleTorrentDownloadFailed);
|
||||||
|
|
||||||
// initialise the smart episode regex
|
|
||||||
const QString regex = computeSmartFilterRegex(smartEpisodeFilters());
|
|
||||||
m_smartEpisodeRegex = QRegularExpression(regex,
|
|
||||||
QRegularExpression::CaseInsensitiveOption
|
|
||||||
| QRegularExpression::ExtendedPatternSyntaxOption
|
|
||||||
| QRegularExpression::UseUnicodePropertiesOption);
|
|
||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
||||||
m_processingTimer->setSingleShot(true);
|
m_processingTimer->setSingleShot(true);
|
||||||
@@ -240,7 +226,7 @@ void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFi
|
|||||||
QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||||
{
|
{
|
||||||
QJsonObject jsonObj;
|
QJsonObject jsonObj;
|
||||||
for (const auto &rule : copyAsConst(rules()))
|
for (const auto &rule : rules())
|
||||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||||
|
|
||||||
return QJsonDocument(jsonObj).toJson();
|
return QJsonDocument(jsonObj).toJson();
|
||||||
@@ -248,14 +234,15 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
|||||||
|
|
||||||
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
|
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);
|
insertRule(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
||||||
{
|
{
|
||||||
QVariantHash dict;
|
QVariantHash dict;
|
||||||
for (const auto &rule : copyAsConst(rules()))
|
for (const auto &rule : rules())
|
||||||
dict[rule.name()] = rule.toLegacyDict();
|
dict[rule.name()] = rule.toLegacyDict();
|
||||||
|
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
@@ -275,41 +262,10 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
|
|||||||
if (in.status() != QDataStream::Ok)
|
if (in.status() != QDataStream::Ok)
|
||||||
throw ParsingError(tr("Invalid data format"));
|
throw ParsingError(tr("Invalid data format"));
|
||||||
|
|
||||||
for (const QVariant &val : qAsConst(dict))
|
for (const QVariant &val : dict)
|
||||||
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
|
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()
|
void AutoDownloader::process()
|
||||||
{
|
{
|
||||||
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
||||||
@@ -365,8 +321,18 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
|||||||
for (AutoDownloadRule &rule: m_rules) {
|
for (AutoDownloadRule &rule: m_rules) {
|
||||||
if (!rule.isEnabled()) continue;
|
if (!rule.isEnabled()) continue;
|
||||||
if (!rule.feedURLs().contains(job->feedURL)) continue;
|
if (!rule.feedURLs().contains(job->feedURL)) continue;
|
||||||
if (!rule.accepts(job->articleData)) continue;
|
if (!rule.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;
|
m_dirty = true;
|
||||||
storeDeferred();
|
storeDeferred();
|
||||||
|
|
||||||
@@ -374,8 +340,6 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
|||||||
params.savePath = rule.savePath();
|
params.savePath = rule.savePath();
|
||||||
params.category = rule.assignedCategory();
|
params.category = rule.assignedCategory();
|
||||||
params.addPaused = rule.addPaused();
|
params.addPaused = rule.addPaused();
|
||||||
if (!rule.savePath().isEmpty())
|
|
||||||
params.useAutoTMM = TriStateBool::False;
|
|
||||||
auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
|
auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
|
||||||
BitTorrent::Session::instance()->addTorrent(torrentURL, params);
|
BitTorrent::Session::instance()->addTorrent(torrentURL, params);
|
||||||
|
|
||||||
@@ -405,7 +369,7 @@ void AutoDownloader::load()
|
|||||||
loadRules(rulesFile.readAll());
|
loadRules(rulesFile.readAll());
|
||||||
else
|
else
|
||||||
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
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)
|
void AutoDownloader::loadRules(const QByteArray &data)
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
@@ -59,7 +58,7 @@ namespace RSS
|
|||||||
QString message() const;
|
QString message() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AutoDownloader final : public QObject
|
class AutoDownloader final: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(AutoDownloader)
|
Q_DISABLE_COPY(AutoDownloader)
|
||||||
@@ -81,10 +80,6 @@ namespace RSS
|
|||||||
bool isProcessingEnabled() const;
|
bool isProcessingEnabled() const;
|
||||||
void setProcessingEnabled(bool enabled);
|
void setProcessingEnabled(bool enabled);
|
||||||
|
|
||||||
QStringList smartEpisodeFilters() const;
|
|
||||||
void setSmartEpisodeFilters(const QStringList &filters);
|
|
||||||
QRegularExpression smartEpisodeRegex() const;
|
|
||||||
|
|
||||||
bool hasRule(const QString &ruleName) const;
|
bool hasRule(const QString &ruleName) const;
|
||||||
AutoDownloadRule ruleByName(const QString &ruleName) const;
|
AutoDownloadRule ruleByName(const QString &ruleName) const;
|
||||||
QList<AutoDownloadRule> rules() const;
|
QList<AutoDownloadRule> rules() const;
|
||||||
@@ -137,6 +132,5 @@ namespace RSS
|
|||||||
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
|
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
|
||||||
bool m_dirty = false;
|
bool m_dirty = false;
|
||||||
QBasicTimer m_savingTimer;
|
QBasicTimer m_savingTimer;
|
||||||
QRegularExpression m_smartEpisodeRegex;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,19 +34,18 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QRegExp>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSharedData>
|
#include <QSharedData>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
#include "../global.h"
|
|
||||||
#include "../preferences.h"
|
#include "../preferences.h"
|
||||||
#include "../tristatebool.h"
|
#include "../tristatebool.h"
|
||||||
#include "../utils/fs.h"
|
#include "../utils/fs.h"
|
||||||
#include "../utils/string.h"
|
#include "../utils/string.h"
|
||||||
#include "rss_article.h"
|
|
||||||
#include "rss_autodownloader.h"
|
|
||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
|
#include "rss_article.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -101,12 +100,10 @@ const QString Str_AssignedCategory(QStringLiteral("assignedCategory"));
|
|||||||
const QString Str_LastMatch(QStringLiteral("lastMatch"));
|
const QString Str_LastMatch(QStringLiteral("lastMatch"));
|
||||||
const QString Str_IgnoreDays(QStringLiteral("ignoreDays"));
|
const QString Str_IgnoreDays(QStringLiteral("ignoreDays"));
|
||||||
const QString Str_AddPaused(QStringLiteral("addPaused"));
|
const QString Str_AddPaused(QStringLiteral("addPaused"));
|
||||||
const QString Str_SmartFilter(QStringLiteral("smartFilter"));
|
|
||||||
const QString Str_PreviouslyMatched(QStringLiteral("previouslyMatchedEpisodes"));
|
|
||||||
|
|
||||||
namespace RSS
|
namespace RSS
|
||||||
{
|
{
|
||||||
struct AutoDownloadRuleData : public QSharedData
|
struct AutoDownloadRuleData: public QSharedData
|
||||||
{
|
{
|
||||||
QString name;
|
QString name;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
@@ -123,10 +120,6 @@ namespace RSS
|
|||||||
QString category;
|
QString category;
|
||||||
TriStateBool addPaused = TriStateBool::Undefined;
|
TriStateBool addPaused = TriStateBool::Undefined;
|
||||||
|
|
||||||
bool smartFilter = false;
|
|
||||||
QStringList previouslyMatchedEpisodes;
|
|
||||||
|
|
||||||
mutable QString lastComputedEpisode;
|
|
||||||
mutable QHash<QString, QRegularExpression> cachedRegexes;
|
mutable QHash<QString, QRegularExpression> cachedRegexes;
|
||||||
|
|
||||||
bool operator==(const AutoDownloadRuleData &other) const
|
bool operator==(const AutoDownloadRuleData &other) const
|
||||||
@@ -142,38 +135,13 @@ namespace RSS
|
|||||||
&& (lastMatch == other.lastMatch)
|
&& (lastMatch == other.lastMatch)
|
||||||
&& (savePath == other.savePath)
|
&& (savePath == other.savePath)
|
||||||
&& (category == other.category)
|
&& (category == other.category)
|
||||||
&& (addPaused == other.addPaused)
|
&& (addPaused == other.addPaused);
|
||||||
&& (smartFilter == other.smartFilter);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace RSS;
|
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)
|
AutoDownloadRule::AutoDownloadRule(const QString &name)
|
||||||
: m_dataPtr(new AutoDownloadRuleData)
|
: 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
|
// The cache is cleared whenever the regex/wildcard, must or must not contain fields or
|
||||||
// episode filter are modified.
|
// episode filter are modified.
|
||||||
Q_ASSERT(!expression.isEmpty());
|
Q_ASSERT(!expression.isEmpty());
|
||||||
|
QRegularExpression regex(m_dataPtr->cachedRegexes[expression]);
|
||||||
|
|
||||||
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
if (!regex.pattern().isEmpty())
|
||||||
if (regex.pattern().isEmpty()) {
|
return regex;
|
||||||
regex = QRegularExpression {
|
|
||||||
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
|
|
||||||
, QRegularExpression::CaseInsensitiveOption};
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
if (expression.isEmpty()) {
|
||||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (m_dataPtr->useRegex) {
|
||||||
if (m_dataPtr->useRegex) {
|
|
||||||
QRegularExpression reg(cachedRegex(expression));
|
QRegularExpression reg(cachedRegex(expression));
|
||||||
return reg.match(articleTitle).hasMatch();
|
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.
|
if (!reg.match(articleTitle).hasMatch())
|
||||||
// Order of wildcard tokens is unimportant (if order is important, they should have used *).
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle) const
|
bool AutoDownloadRule::matches(const QString &articleTitle) const
|
||||||
{
|
{
|
||||||
if (m_dataPtr->mustContain.empty())
|
if (!m_dataPtr->mustContain.empty()) {
|
||||||
return true;
|
bool logged = false;
|
||||||
|
bool foundMustContain = false;
|
||||||
|
|
||||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||||
// Accept if any complete expression matches.
|
// Accept if any complete expression matches.
|
||||||
for (const QString &expression : qAsConst(m_dataPtr->mustContain)) {
|
foreach (const QString &expression, m_dataPtr->mustContain) {
|
||||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
if (!logged) {
|
||||||
if (matchesExpression(articleTitle, expression))
|
// qDebug() << "Checking matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expressions:") << m_dataPtr->mustContain.join("|");
|
||||||
return true;
|
logged = 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matched) {
|
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||||
const int seasonTheirs {matcher.captured(1).toInt()};
|
foundMustContain = matches(articleTitle, expression);
|
||||||
const int episodeTheirs {matcher.captured(2).toInt()};
|
|
||||||
|
|
||||||
if (episode.endsWith('-')) { // Infinite range
|
if (foundMustContain) {
|
||||||
const int episodeOurs {episode.leftRef(episode.size() - 1).toInt()};
|
// qDebug() << "Found matching" << (m_dataPtr->useRegex ? "regex:" : "wildcard expression:") << expression;
|
||||||
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
|
break;
|
||||||
return true;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
else { // Normal range
|
||||||
const QStringList range {episode.split('-')};
|
QStringList range = ep.split('-');
|
||||||
Q_ASSERT(range.size() == 2);
|
Q_ASSERT(range.size() == 2);
|
||||||
if (range.first().toInt() > range.last().toInt())
|
if (range.first().toInt() > range.last().toInt())
|
||||||
continue; // Ignore this subrule completely
|
continue; // Ignore this subrule completely
|
||||||
|
|
||||||
const int episodeOursFirst {range.first().toInt()};
|
int epOursFirst = range.first().toInt();
|
||||||
const int episodeOursLast {range.last().toInt()};
|
int epOursLast = range.last().toInt();
|
||||||
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
|
|
||||||
return true;
|
// 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)};
|
return false;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qDebug() << "Matched article:" << articleTitle;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,9 +367,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
|
|||||||
, {Str_AssignedCategory, assignedCategory()}
|
, {Str_AssignedCategory, assignedCategory()}
|
||||||
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
|
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
|
||||||
, {Str_IgnoreDays, ignoreDays()}
|
, {Str_IgnoreDays, ignoreDays()}
|
||||||
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())}
|
, {Str_AddPaused, triStateBoolToJsonValue(addPaused())}};
|
||||||
, {Str_SmartFilter, useSmartFilter()}
|
|
||||||
, {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, const QString &name)
|
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.setAddPaused(jsonValueToTriStateBool(jsonObj.value(Str_AddPaused)));
|
||||||
rule.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date));
|
rule.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date));
|
||||||
rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt());
|
rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt());
|
||||||
rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false));
|
|
||||||
|
|
||||||
const QJsonValue feedsVal = jsonObj.value(Str_AffectedFeeds);
|
const QJsonValue feedsVal = jsonObj.value(Str_AffectedFeeds);
|
||||||
QStringList feedURLs;
|
QStringList feedURLs;
|
||||||
@@ -446,17 +393,6 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
|||||||
feedURLs << urlVal.toString();
|
feedURLs << urlVal.toString();
|
||||||
rule.setFeedURLs(feedURLs);
|
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;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,16 +549,6 @@ QString AutoDownloadRule::mustNotContain() const
|
|||||||
return m_dataPtr->mustNotContain.join("|");
|
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
|
bool AutoDownloadRule::useRegex() const
|
||||||
{
|
{
|
||||||
return m_dataPtr->useRegex;
|
return m_dataPtr->useRegex;
|
||||||
@@ -634,16 +560,6 @@ void AutoDownloadRule::setUseRegex(bool enabled)
|
|||||||
m_dataPtr->cachedRegexes.clear();
|
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
|
QString AutoDownloadRule::episodeFilter() const
|
||||||
{
|
{
|
||||||
return m_dataPtr->episodeFilter;
|
return m_dataPtr->episodeFilter;
|
||||||
|
|||||||
@@ -66,14 +66,9 @@ namespace RSS
|
|||||||
void setLastMatch(const QDateTime &lastMatch);
|
void setLastMatch(const QDateTime &lastMatch);
|
||||||
bool useRegex() const;
|
bool useRegex() const;
|
||||||
void setUseRegex(bool enabled);
|
void setUseRegex(bool enabled);
|
||||||
bool useSmartFilter() const;
|
|
||||||
void setUseSmartFilter(bool enabled);
|
|
||||||
QString episodeFilter() const;
|
QString episodeFilter() const;
|
||||||
void setEpisodeFilter(const QString &e);
|
void setEpisodeFilter(const QString &e);
|
||||||
|
|
||||||
QStringList previouslyMatchedEpisodes() const;
|
|
||||||
void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes);
|
|
||||||
|
|
||||||
QString savePath() const;
|
QString savePath() const;
|
||||||
void setSavePath(const QString &savePath);
|
void setSavePath(const QString &savePath);
|
||||||
TriStateBool addPaused() const;
|
TriStateBool addPaused() const;
|
||||||
@@ -81,8 +76,7 @@ namespace RSS
|
|||||||
QString assignedCategory() const;
|
QString assignedCategory() const;
|
||||||
void setCategory(const QString &category);
|
void setCategory(const QString &category);
|
||||||
|
|
||||||
bool matches(const QVariantHash &articleData) const;
|
bool matches(const QString &articleTitle) const;
|
||||||
bool accepts(const QVariantHash &articleData);
|
|
||||||
|
|
||||||
AutoDownloadRule &operator=(const AutoDownloadRule &other);
|
AutoDownloadRule &operator=(const AutoDownloadRule &other);
|
||||||
bool operator==(const AutoDownloadRule &other) const;
|
bool operator==(const AutoDownloadRule &other) const;
|
||||||
@@ -95,11 +89,7 @@ namespace RSS
|
|||||||
static AutoDownloadRule fromLegacyDict(const QVariantHash &dict);
|
static AutoDownloadRule fromLegacyDict(const QVariantHash &dict);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool matchesMustContainExpression(const QString &articleTitle) const;
|
bool matches(const QString &articleTitle, const QString &expression) 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;
|
|
||||||
QRegularExpression cachedRegex(const QString &expression, bool isRegex = true) const;
|
QRegularExpression cachedRegex(const QString &expression, bool isRegex = true) const;
|
||||||
|
|
||||||
QSharedDataPointer<AutoDownloadRuleData> m_dataPtr;
|
QSharedDataPointer<AutoDownloadRuleData> m_dataPtr;
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ void Feed::handleDownloadFailed(const QString &url, const QString &error)
|
|||||||
m_isLoading = false;
|
m_isLoading = false;
|
||||||
m_hasError = true;
|
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);
|
, Log::WARNING);
|
||||||
|
|
||||||
emit stateChanged(this);
|
emit stateChanged(this);
|
||||||
@@ -197,13 +197,12 @@ void Feed::handleDownloadFailed(const QString &url, const QString &error)
|
|||||||
|
|
||||||
void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
||||||
{
|
{
|
||||||
m_hasError = !result.error.isEmpty();
|
if (!result.error.isEmpty()) {
|
||||||
|
m_hasError = true;
|
||||||
// For some reason, the RSS feed may contain malformed XML data and it may not be
|
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url).arg(result.error)
|
||||||
// successfully parsed by the XML parser. We are still trying to load as many articles
|
, Log::WARNING);
|
||||||
// as possible until we encounter corrupted data. So we can have some articles here
|
}
|
||||||
// even in case of parsing error.
|
else {
|
||||||
if (!m_hasError || !result.articles.isEmpty()) {
|
|
||||||
if (title() != result.title) {
|
if (title() != result.title) {
|
||||||
m_title = result.title;
|
m_title = result.title;
|
||||||
emit titleChanged(this);
|
emit titleChanged(this);
|
||||||
@@ -212,13 +211,7 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
|||||||
m_lastBuildDate = result.lastBuildDate;
|
m_lastBuildDate = result.lastBuildDate;
|
||||||
|
|
||||||
int newArticlesCount = 0;
|
int newArticlesCount = 0;
|
||||||
const QDateTime now {QDateTime::currentDateTime()};
|
foreach (const QVariantHash &varHash, result.articles) {
|
||||||
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;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto article = new Article(this, varHash);
|
auto article = new Article(this, varHash);
|
||||||
if (addArticle(article))
|
if (addArticle(article))
|
||||||
@@ -230,15 +223,11 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_dirty = (newArticlesCount > 0);
|
m_dirty = (newArticlesCount > 0);
|
||||||
|
|
||||||
store();
|
store();
|
||||||
|
m_hasError = false;
|
||||||
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
|
LogMsg(tr("RSS feed at '%1' successfully updated. Added %2 new articles.")
|
||||||
.arg(m_url, QString::number(newArticlesCount)));
|
.arg(m_url).arg(newArticlesCount));
|
||||||
}
|
|
||||||
|
|
||||||
if (m_hasError) {
|
|
||||||
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
|
|
||||||
, Log::WARNING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_isLoading = false;
|
m_isLoading = false;
|
||||||
@@ -260,7 +249,7 @@ void Feed::load()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
|
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
|
||||||
.arg(m_dataFileName, file.errorString())
|
.arg(m_dataFileName).arg(file.errorString())
|
||||||
, Log::WARNING);
|
, Log::WARNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +389,7 @@ void Feed::downloadIcon()
|
|||||||
// Download the RSS Feed icon
|
// Download the RSS Feed icon
|
||||||
// XXX: This works for most sites but it is not perfect
|
// XXX: This works for most sites but it is not perfect
|
||||||
const QUrl url(m_url);
|
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);
|
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl, true);
|
||||||
connect(handler
|
connect(handler
|
||||||
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace RSS
|
|||||||
struct ParsingResult;
|
struct ParsingResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Feed final : public Item
|
class Feed final: public Item
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Feed)
|
Q_DISABLE_COPY(Feed)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
|
||||||
#include "base/global.h"
|
|
||||||
#include "rss_article.h"
|
#include "rss_article.h"
|
||||||
|
|
||||||
using namespace RSS;
|
using namespace RSS;
|
||||||
@@ -123,7 +122,7 @@ void Folder::addItem(Item *item)
|
|||||||
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
|
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
|
||||||
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
|
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
|
||||||
|
|
||||||
for (auto article: copyAsConst(item->articles()))
|
for (auto article: item->articles())
|
||||||
emit newArticle(article);
|
emit newArticle(article);
|
||||||
|
|
||||||
if (item->unreadCount() > 0)
|
if (item->unreadCount() > 0)
|
||||||
@@ -134,7 +133,7 @@ void Folder::removeItem(Item *item)
|
|||||||
{
|
{
|
||||||
Q_ASSERT(m_items.contains(item));
|
Q_ASSERT(m_items.contains(item));
|
||||||
|
|
||||||
for (auto article: copyAsConst(item->articles()))
|
for (auto article: item->articles())
|
||||||
emit articleAboutToBeRemoved(article);
|
emit articleAboutToBeRemoved(article);
|
||||||
|
|
||||||
item->disconnect(this);
|
item->disconnect(this);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace RSS
|
|||||||
{
|
{
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
class Folder final : public Item
|
class Folder final: public Item
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Folder)
|
Q_DISABLE_COPY(Folder)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
using namespace RSS;
|
using namespace RSS;
|
||||||
|
|
||||||
const QChar Item::PathSeparator('\\');
|
const QString Item::PathSeparator("\\");
|
||||||
|
|
||||||
Item::Item(const QString &path)
|
Item::Item(const QString &path)
|
||||||
: m_path(path)
|
: m_path(path)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace RSS
|
|||||||
class Folder;
|
class Folder;
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
class Item : public QObject
|
class Item: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Item)
|
Q_DISABLE_COPY(Item)
|
||||||
@@ -58,7 +58,7 @@ namespace RSS
|
|||||||
|
|
||||||
virtual QJsonValue toJsonValue(bool withData = false) const = 0;
|
virtual QJsonValue toJsonValue(bool withData = false) const = 0;
|
||||||
|
|
||||||
static const QChar PathSeparator;
|
static const QString PathSeparator;
|
||||||
|
|
||||||
static bool isValidPath(const QString &path);
|
static bool isValidPath(const QString &path);
|
||||||
static QString joinPath(const QString &path1, const QString &path2);
|
static QString joinPath(const QString &path1, const QString &path2);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
#include "../utils/fs.h"
|
#include "../utils/fs.h"
|
||||||
#include "rss_article.h"
|
#include "rss_article.h"
|
||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
#include "rss_folder.h"
|
|
||||||
#include "rss_item.h"
|
#include "rss_item.h"
|
||||||
|
#include "rss_folder.h"
|
||||||
|
|
||||||
const int MsecsPerMin = 60000;
|
const int MsecsPerMin = 60000;
|
||||||
const QString ConfFolderName(QStringLiteral("rss"));
|
const QString ConfFolderName(QStringLiteral("rss"));
|
||||||
@@ -79,7 +79,7 @@ Session::Session()
|
|||||||
connect(m_confFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
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")
|
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(
|
m_dataFileStorage = new AsyncFileStorage(
|
||||||
@@ -89,7 +89,7 @@ Session::Session()
|
|||||||
connect(m_dataFileStorage, &AsyncFileStorage::failed, [](const QString &fileName, const QString &errorString)
|
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")
|
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
|
m_itemsByPath.insert("", new Folder); // root folder
|
||||||
@@ -257,7 +257,7 @@ void Session::load()
|
|||||||
if (!itemsFile.open(QFile::ReadOnly)) {
|
if (!itemsFile.open(QFile::ReadOnly)) {
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
QString("Couldn't read RSS Session data from %1. Error: %2")
|
QString("Couldn't read RSS Session data from %1. Error: %2")
|
||||||
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
|
.arg(itemsFile.fileName()).arg(itemsFile.errorString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ void Session::load()
|
|||||||
if (jsonError.error != QJsonParseError::NoError) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
QString("Couldn't parse RSS Session data from %1. Error: %2")
|
QString("Couldn't parse RSS Session data from %1. Error: %2")
|
||||||
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
|
.arg(itemsFile.fileName()).arg(jsonError.errorString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
|||||||
else if (!val.isObject()) {
|
else if (!val.isObject()) {
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
QString("Couldn't load RSS Item '%1'. Invalid data format.")
|
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 {
|
else {
|
||||||
QJsonObject valObj = val.toObject();
|
QJsonObject valObj = val.toObject();
|
||||||
@@ -301,7 +301,7 @@ void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
|||||||
if (!valObj["url"].isString()) {
|
if (!valObj["url"].isString()) {
|
||||||
Logger::instance()->addMessage(
|
Logger::instance()->addMessage(
|
||||||
QString("Couldn't load RSS Feed '%1'. URL is required.")
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ class AsyncFileStorage;
|
|||||||
|
|
||||||
namespace RSS
|
namespace RSS
|
||||||
{
|
{
|
||||||
|
class Item;
|
||||||
class Feed;
|
class Feed;
|
||||||
class Folder;
|
class Folder;
|
||||||
class Item;
|
|
||||||
|
|
||||||
class Session : public QObject
|
class Session: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(Session)
|
Q_DISABLE_COPY(Session)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "scanfoldersmodel.h"
|
#include "scanfoldersmodel.h"
|
||||||
@@ -54,7 +56,7 @@ struct ScanFoldersModel::PathData
|
|||||||
QString downloadPath; // valid for CUSTOM_LOCATION
|
QString downloadPath; // valid for CUSTOM_LOCATION
|
||||||
};
|
};
|
||||||
|
|
||||||
ScanFoldersModel *ScanFoldersModel::m_instance = nullptr;
|
ScanFoldersModel *ScanFoldersModel::m_instance = 0;
|
||||||
|
|
||||||
bool ScanFoldersModel::initInstance(QObject *parent)
|
bool ScanFoldersModel::initInstance(QObject *parent)
|
||||||
{
|
{
|
||||||
@@ -70,7 +72,7 @@ void ScanFoldersModel::freeInstance()
|
|||||||
{
|
{
|
||||||
if (m_instance) {
|
if (m_instance) {
|
||||||
delete m_instance;
|
delete m_instance;
|
||||||
m_instance = nullptr;
|
m_instance = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,10 +83,10 @@ ScanFoldersModel *ScanFoldersModel::instance()
|
|||||||
|
|
||||||
ScanFoldersModel::ScanFoldersModel(QObject *parent)
|
ScanFoldersModel::ScanFoldersModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_fsWatcher(nullptr)
|
, m_fsWatcher(0)
|
||||||
{
|
{
|
||||||
configure();
|
configure();
|
||||||
connect(Preferences::instance(), &Preferences::changed, this, &ScanFoldersModel::configure);
|
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ScanFoldersModel::~ScanFoldersModel()
|
ScanFoldersModel::~ScanFoldersModel()
|
||||||
@@ -220,7 +222,7 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
|||||||
|
|
||||||
if (!m_fsWatcher) {
|
if (!m_fsWatcher) {
|
||||||
m_fsWatcher = new FileSystemWatcher(this);
|
m_fsWatcher = new FileSystemWatcher(this);
|
||||||
connect(m_fsWatcher, &FileSystemWatcher::torrentsAdded, this, &ScanFoldersModel::addTorrentsToSession);
|
connect(m_fsWatcher, SIGNAL(torrentsAdded(const QStringList &)), this, SLOT(addTorrentsToSession(const QStringList &)));
|
||||||
}
|
}
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||||
@@ -233,7 +235,7 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
|||||||
return Ok;
|
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);
|
QDir watchDir(watchPath);
|
||||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||||
@@ -338,7 +340,7 @@ void ScanFoldersModel::makePersistent()
|
|||||||
|
|
||||||
void ScanFoldersModel::configure()
|
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) {
|
for (QVariantHash::const_iterator i = dirs.begin(), e = dirs.end(); i != e; ++i) {
|
||||||
if (i.value().type() == QVariant::Int)
|
if (i.value().type() == QVariant::Int)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* 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),
|
* 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
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
|
*
|
||||||
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SCANFOLDERSMODEL_H
|
#ifndef SCANFOLDERSMODEL_H
|
||||||
@@ -35,7 +37,7 @@
|
|||||||
class QStringList;
|
class QStringList;
|
||||||
class FileSystemWatcher;
|
class FileSystemWatcher;
|
||||||
|
|
||||||
class ScanFoldersModel : public QAbstractListModel
|
class ScanFoldersModel: public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(ScanFoldersModel)
|
Q_DISABLE_COPY(ScanFoldersModel)
|
||||||
@@ -64,9 +66,9 @@ public:
|
|||||||
CUSTOM_LOCATION
|
CUSTOM_LOCATION
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool initInstance(QObject *parent = nullptr);
|
static bool initInstance(QObject *parent = 0);
|
||||||
static void freeInstance();
|
static void freeInstance();
|
||||||
static ScanFoldersModel *instance();
|
static ScanFoldersModel* instance();
|
||||||
|
|
||||||
static QString pathTypeDisplayName(const PathType type);
|
static QString pathTypeDisplayName(const PathType type);
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ private slots:
|
|||||||
void addTorrentsToSession(const QStringList &pathList);
|
void addTorrentsToSession(const QStringList &pathList);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ScanFoldersModel(QObject *parent = nullptr);
|
explicit ScanFoldersModel(QObject *parent = 0);
|
||||||
~ScanFoldersModel();
|
~ScanFoldersModel();
|
||||||
|
|
||||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
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