mirror of
https://github.com/nzbget/nzbget.git
synced 2025-12-24 06:37:44 -05:00
Compare commits
1209 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0d35f9a09 | ||
|
|
ce7cd631c2 | ||
|
|
0432cf13d3 | ||
|
|
799de88b3e | ||
|
|
7ff3251dcf | ||
|
|
97ae03bbd3 | ||
|
|
6bbfb6b7b7 | ||
|
|
f02bbbefd7 | ||
|
|
4d19c899bd | ||
|
|
1d008bd1f5 | ||
|
|
8c1e62ef49 | ||
|
|
e18c25c231 | ||
|
|
6dbe6edbab | ||
|
|
1ee8e02586 | ||
|
|
f8f9dd2b6d | ||
|
|
414ffcbc35 | ||
|
|
0522b5f49d | ||
|
|
575b823758 | ||
|
|
a124a91a84 | ||
|
|
4546b0f368 | ||
|
|
625e7a61e1 | ||
|
|
f1c1373c7d | ||
|
|
5dda6b2e49 | ||
|
|
81aa56324f | ||
|
|
a8533e7f0a | ||
|
|
bbfcf07689 | ||
|
|
fd35e05b61 | ||
|
|
3e0be12cb3 | ||
|
|
d6e8f67927 | ||
|
|
a159a1ff5a | ||
|
|
15f4955f38 | ||
|
|
aac98b53ee | ||
|
|
fa4a5bb261 | ||
|
|
d19c9b80e7 | ||
|
|
c7716ae9b7 | ||
|
|
e07a6b9443 | ||
|
|
fa57474d78 | ||
|
|
4299ac1354 | ||
|
|
82dfec471b | ||
|
|
3a5bc85962 | ||
|
|
25dc60e71f | ||
|
|
855f3e8649 | ||
|
|
bdc7ba38db | ||
|
|
89427f42ce | ||
|
|
a665dc5375 | ||
|
|
adf3e05e1d | ||
|
|
e3bd94189a | ||
|
|
bb1cb68653 | ||
|
|
e91f37d566 | ||
|
|
57f4d2864b | ||
|
|
05c841880f | ||
|
|
92828acab0 | ||
|
|
137c936830 | ||
|
|
4826f04778 | ||
|
|
15b4f55310 | ||
|
|
0461f2ad55 | ||
|
|
67ca371c6b | ||
|
|
a97a6d7c7f | ||
|
|
b6927e992e | ||
|
|
59cae49344 | ||
|
|
6bf097f1c3 | ||
|
|
ad0592843c | ||
|
|
8a09de775f | ||
|
|
adf7ec225b | ||
|
|
d15722c72d | ||
|
|
0776c6b057 | ||
|
|
8f63eef312 | ||
|
|
8a59079627 | ||
|
|
491d816bff | ||
|
|
6dfe17c1d8 | ||
|
|
f3cf9317a6 | ||
|
|
b0356d88d6 | ||
|
|
c0d7a15afa | ||
|
|
fbfa793b20 | ||
|
|
a329c65eb3 | ||
|
|
b9c4c5b19e | ||
|
|
a5f2c1c7c5 | ||
|
|
e2ea481799 | ||
|
|
c2b93c588b | ||
|
|
3934244a70 | ||
|
|
62ba9a5609 | ||
|
|
e7d4556f8b | ||
|
|
43c9bb78f3 | ||
|
|
e824c5b940 | ||
|
|
32a6bf18ad | ||
|
|
2cb419691d | ||
|
|
a74722d8cc | ||
|
|
0602e9d2f1 | ||
|
|
9713cbad5e | ||
|
|
009cf9eee2 | ||
|
|
85995ad56f | ||
|
|
1f3067c1e3 | ||
|
|
794f240f48 | ||
|
|
49e8fea0e2 | ||
|
|
fa8f8855f9 | ||
|
|
2c85def959 | ||
|
|
fb3a27fde9 | ||
|
|
b29131ffb8 | ||
|
|
31a34b58ea | ||
|
|
4a10fdb2df | ||
|
|
541a695e2f | ||
|
|
07b7a766a2 | ||
|
|
1057e9194c | ||
|
|
9eaf9fae9a | ||
|
|
34d157990d | ||
|
|
c93eb2087f | ||
|
|
1f89c037b9 | ||
|
|
20036b73b8 | ||
|
|
da3425af3f | ||
|
|
4c482a91da | ||
|
|
458a1afb13 | ||
|
|
3339a2c520 | ||
|
|
17c5a9cbc8 | ||
|
|
4db9ef2535 | ||
|
|
5a0eae7bf4 | ||
|
|
86ac23b6aa | ||
|
|
fa1aa45fa7 | ||
|
|
f842a19544 | ||
|
|
f3cb44e7b2 | ||
|
|
e54ffbaaaa | ||
|
|
2d049f1904 | ||
|
|
5106979d5d | ||
|
|
75d05bce4a | ||
|
|
ea4ea2c901 | ||
|
|
5e15677218 | ||
|
|
93ad31b9d8 | ||
|
|
14c5a1caf7 | ||
|
|
f52f5b5de9 | ||
|
|
0916c2a908 | ||
|
|
ab1238dde4 | ||
|
|
758ce4047b | ||
|
|
c24bf0e8ce | ||
|
|
1264878a97 | ||
|
|
a85ff314f3 | ||
|
|
a349ab08f7 | ||
|
|
064de49edf | ||
|
|
ae79c56c07 | ||
|
|
d6353e9cee | ||
|
|
d9d824631e | ||
|
|
2bd765b06f | ||
|
|
f51c216417 | ||
|
|
78b270d23e | ||
|
|
a4252a1e79 | ||
|
|
1ac2be47d5 | ||
|
|
9437a227ee | ||
|
|
0d19722881 | ||
|
|
adfe5eef26 | ||
|
|
321cddeeba | ||
|
|
44f08325f9 | ||
|
|
e601e77e5e | ||
|
|
8e6ccfa8a7 | ||
|
|
3eebee20aa | ||
|
|
b83a9b9aff | ||
|
|
05d7a8ede2 | ||
|
|
4d771036e2 | ||
|
|
7e659d8d97 | ||
|
|
137ac1a3ee | ||
|
|
3a4e6623db | ||
|
|
c2669b359e | ||
|
|
8bffb51974 | ||
|
|
81be21b540 | ||
|
|
222d6a1f6d | ||
|
|
cd6bf682f9 | ||
|
|
d93769021a | ||
|
|
cf0d086b57 | ||
|
|
bf53c6eaa6 | ||
|
|
9b50760006 | ||
|
|
b7102894d7 | ||
|
|
db102f5a15 | ||
|
|
d9cb0026bd | ||
|
|
5893d03f1b | ||
|
|
18d138648b | ||
|
|
93a43e711f | ||
|
|
2b52dc5bfe | ||
|
|
ce844367e7 | ||
|
|
64a5a78866 | ||
|
|
6f9fb29595 | ||
|
|
0ee60ab844 | ||
|
|
8dfca2a542 | ||
|
|
76bdd63e60 | ||
|
|
ef78cbfc74 | ||
|
|
74768b2183 | ||
|
|
801bf1ae7c | ||
|
|
a901deff03 | ||
|
|
67245d6ca8 | ||
|
|
2d70e1de21 | ||
|
|
7deb3c1b68 | ||
|
|
d4886ac7d1 | ||
|
|
5b3372107d | ||
|
|
07c54740a7 | ||
|
|
af111adbde | ||
|
|
d31a734a5c | ||
|
|
54f14f5efa | ||
|
|
18fbd12f2c | ||
|
|
ff671e722d | ||
|
|
15c292653e | ||
|
|
c0aed9af48 | ||
|
|
597e4fd034 | ||
|
|
3c2575bc26 | ||
|
|
50c1ca588c | ||
|
|
da9c8b1138 | ||
|
|
c59ab2d9dc | ||
|
|
35fca1479c | ||
|
|
54c5a061c8 | ||
|
|
3a0489a4a9 | ||
|
|
a31fb733a2 | ||
|
|
2691eff535 | ||
|
|
37b04c593a | ||
|
|
b9b1c76ada | ||
|
|
69a0db63f6 | ||
|
|
e9926d92e0 | ||
|
|
f5aa27979c | ||
|
|
24a4542c14 | ||
|
|
bb95e1f274 | ||
|
|
186da63056 | ||
|
|
1facedb694 | ||
|
|
54eb8e1291 | ||
|
|
80b67383e3 | ||
|
|
406a78218a | ||
|
|
262df77f74 | ||
|
|
71505340d0 | ||
|
|
bddb0bb26d | ||
|
|
d90a40909b | ||
|
|
2bdc87c198 | ||
|
|
e97a0fde11 | ||
|
|
eb18608522 | ||
|
|
481e7b3d2b | ||
|
|
8545cb3581 | ||
|
|
e422fea746 | ||
|
|
2ce9f0df38 | ||
|
|
36de095e51 | ||
|
|
9b05f779f6 | ||
|
|
38efd4a4de | ||
|
|
80b8ee8dfb | ||
|
|
47b1c1a2dd | ||
|
|
7417160da9 | ||
|
|
a41e010165 | ||
|
|
cbe7b1e051 | ||
|
|
00a5b68d84 | ||
|
|
561713dbed | ||
|
|
cce9338909 | ||
|
|
515fd9298d | ||
|
|
0ee72d2dd7 | ||
|
|
57f932cfab | ||
|
|
8f803c2f21 | ||
|
|
89bd5d6dfe | ||
|
|
49a0292053 | ||
|
|
a60d8d1273 | ||
|
|
44ea3d02ab | ||
|
|
fe9f208f20 | ||
|
|
20e8bb6ebc | ||
|
|
0709f248ee | ||
|
|
35d8aa5fa7 | ||
|
|
9f80f45fb9 | ||
|
|
763fe425d6 | ||
|
|
9c86dc70bd | ||
|
|
1f6a360de5 | ||
|
|
dcdc41ca9a | ||
|
|
6d307a05f8 | ||
|
|
83c15b1f05 | ||
|
|
43fc121219 | ||
|
|
4b729eb0f0 | ||
|
|
0158614da2 | ||
|
|
ca7807fa92 | ||
|
|
97018ae102 | ||
|
|
cbe6c6a340 | ||
|
|
d84ec5685b | ||
|
|
557e0580a7 | ||
|
|
43c0bdd9d3 | ||
|
|
86bcb7073c | ||
|
|
6cf0edd278 | ||
|
|
b4bcc82abe | ||
|
|
6fb1ea1cff | ||
|
|
3ee9125100 | ||
|
|
fad2be0e0f | ||
|
|
2763f1a522 | ||
|
|
1214c79eab | ||
|
|
7ee0b60361 | ||
|
|
0135e605a8 | ||
|
|
546324d891 | ||
|
|
43563e8dfb | ||
|
|
4f5d357e3c | ||
|
|
a6c120bc82 | ||
|
|
18f673e6b3 | ||
|
|
5ac7c0398e | ||
|
|
0008f040b3 | ||
|
|
45b5727374 | ||
|
|
f001b0744b | ||
|
|
2124a886f8 | ||
|
|
7f4b15b4de | ||
|
|
68c74a5a30 | ||
|
|
01d4ebb800 | ||
|
|
f56e01d200 | ||
|
|
cdc5c5515f | ||
|
|
67195e7683 | ||
|
|
499e3d5d8f | ||
|
|
0ee9083627 | ||
|
|
726a6154be | ||
|
|
c0eedc342b | ||
|
|
eb4b8b30e1 | ||
|
|
593e29f163 | ||
|
|
c4d29bc57f | ||
|
|
92db424ce0 | ||
|
|
17fbb795c8 | ||
|
|
35e65e792b | ||
|
|
486b9d7d2b | ||
|
|
3abaa0fb3f | ||
|
|
810ddc8356 | ||
|
|
1a840f894e | ||
|
|
928e0a6006 | ||
|
|
a4cca673dc | ||
|
|
fb9b84a23b | ||
|
|
bc2b9de6a9 | ||
|
|
7793f64b77 | ||
|
|
cad3116b5b | ||
|
|
dd714355c4 | ||
|
|
0a73a0c31f | ||
|
|
7f393d050c | ||
|
|
5dcca7294f | ||
|
|
53da5725c2 | ||
|
|
77cabd7bce | ||
|
|
2336d4bcfe | ||
|
|
580e1974bc | ||
|
|
8790ee685f | ||
|
|
32f0bbae58 | ||
|
|
8ffd6c24fe | ||
|
|
98cc4817fe | ||
|
|
14b40d6712 | ||
|
|
629640898d | ||
|
|
5df06a2626 | ||
|
|
4ca95b2989 | ||
|
|
b3cc316092 | ||
|
|
298fde6cd4 | ||
|
|
015b3be461 | ||
|
|
f6adbe848d | ||
|
|
08de827d7b | ||
|
|
d1bda91954 | ||
|
|
e40e3178da | ||
|
|
cf3985f228 | ||
|
|
f2329dada4 | ||
|
|
a73d1ba56e | ||
|
|
8d5ce3ddc3 | ||
|
|
d3362f9280 | ||
|
|
85dcde36c5 | ||
|
|
716bb3130f | ||
|
|
725e2c7376 | ||
|
|
3a07dd378a | ||
|
|
ff02b53ed0 | ||
|
|
39089b6f2f | ||
|
|
61af2b3446 | ||
|
|
20f4f3020b | ||
|
|
df5a1fe959 | ||
|
|
6b546394b2 | ||
|
|
160b274ce8 | ||
|
|
4104a2357b | ||
|
|
ba1e51a8d8 | ||
|
|
9f7d6655b2 | ||
|
|
f6a9253a53 | ||
|
|
48020e8901 | ||
|
|
5afa20d655 | ||
|
|
197baf066a | ||
|
|
c873647aae | ||
|
|
fc1847588d | ||
|
|
62b3d47b43 | ||
|
|
ddb9333ca6 | ||
|
|
830e0e4858 | ||
|
|
085c612f97 | ||
|
|
dea9fb2090 | ||
|
|
5813c903eb | ||
|
|
4f499e2c2e | ||
|
|
3884328251 | ||
|
|
7e9e2471ef | ||
|
|
2a433ee7fb | ||
|
|
118f835385 | ||
|
|
9a6a42bd44 | ||
|
|
9434149842 | ||
|
|
4107536c03 | ||
|
|
2aec782f58 | ||
|
|
eaaa943af3 | ||
|
|
964e8311a9 | ||
|
|
895dd12e4d | ||
|
|
d16036aa78 | ||
|
|
8b79a81eaf | ||
|
|
231e94dd2e | ||
|
|
f7be22893d | ||
|
|
3ac91a4bb6 | ||
|
|
43441a8d55 | ||
|
|
b02059f196 | ||
|
|
f107802f0e | ||
|
|
7faf1fe64b | ||
|
|
bf2fea64e7 | ||
|
|
de3eb3de9d | ||
|
|
55d080ac96 | ||
|
|
795bacb4fe | ||
|
|
f7930b56a6 | ||
|
|
b2bf488d59 | ||
|
|
8e4b75b21e | ||
|
|
5b17bebbd6 | ||
|
|
bda1eaa192 | ||
|
|
4bcdbbeb09 | ||
|
|
f0ee12f92e | ||
|
|
0b14a2f869 | ||
|
|
2579ce65b9 | ||
|
|
77f86988cb | ||
|
|
b9b62dcd75 | ||
|
|
bfee7c55cd | ||
|
|
34efa87699 | ||
|
|
e839db7107 | ||
|
|
ffb16aa7bb | ||
|
|
a3b0b7675e | ||
|
|
602f62c17c | ||
|
|
ace7a1968d | ||
|
|
6efefe3780 | ||
|
|
c02f708d74 | ||
|
|
e7c47f523a | ||
|
|
5c8be152f4 | ||
|
|
4cdbfc84dd | ||
|
|
75ec856af3 | ||
|
|
88b5e16597 | ||
|
|
fff6fdd4eb | ||
|
|
c4ff544459 | ||
|
|
9c87942b43 | ||
|
|
0e4c9275b0 | ||
|
|
f11dce4269 | ||
|
|
f1340eb542 | ||
|
|
e89f5cbd8a | ||
|
|
975016700e | ||
|
|
a9017be606 | ||
|
|
d81d6831dc | ||
|
|
1b94373c7e | ||
|
|
d5e881ce1a | ||
|
|
a3f84aca0e | ||
|
|
0ab86b90f0 | ||
|
|
b6a606db35 | ||
|
|
578731f239 | ||
|
|
71db4ffe9c | ||
|
|
7421ec7b1a | ||
|
|
958712663e | ||
|
|
d96fa66487 | ||
|
|
36ac548842 | ||
|
|
fc44ab6128 | ||
|
|
f0da3936e5 | ||
|
|
04e694799d | ||
|
|
712cedb84f | ||
|
|
4fb3ddcf84 | ||
|
|
69d40c11fd | ||
|
|
58893710d8 | ||
|
|
6c6f781510 | ||
|
|
569ec22ee8 | ||
|
|
b06d3eca86 | ||
|
|
dcf63b4db7 | ||
|
|
06e7573572 | ||
|
|
639e0b6bfb | ||
|
|
f2073ff920 | ||
|
|
4ba2e07d94 | ||
|
|
91515b20e5 | ||
|
|
7226e1c186 | ||
|
|
98c2dd46b7 | ||
|
|
f0b08dbd63 | ||
|
|
25ddfd5659 | ||
|
|
7450b97871 | ||
|
|
9f7e0ee972 | ||
|
|
ae7719e948 | ||
|
|
9a92896678 | ||
|
|
ab0723adda | ||
|
|
13b98fca83 | ||
|
|
5901998cb5 | ||
|
|
9ddd73368e | ||
|
|
12daa683e5 | ||
|
|
5f71c4a5a7 | ||
|
|
13db817395 | ||
|
|
84f4cf2f33 | ||
|
|
0d67e322a3 | ||
|
|
374f706354 | ||
|
|
f7cefadf33 | ||
|
|
c111a114b9 | ||
|
|
2cd3f0fc68 | ||
|
|
92db59116e | ||
|
|
760aed68fb | ||
|
|
e612257c28 | ||
|
|
31208a5816 | ||
|
|
a03e7cd550 | ||
|
|
02b33fb559 | ||
|
|
a9ed53daa8 | ||
|
|
a3c460ed40 | ||
|
|
df11d1acb4 | ||
|
|
e49d4c59af | ||
|
|
5dc9c07a58 | ||
|
|
3bb0751c86 | ||
|
|
a3b3921bea | ||
|
|
b19d26aee8 | ||
|
|
7fae337360 | ||
|
|
80debf521a | ||
|
|
528133482e | ||
|
|
a98bbd7d0d | ||
|
|
2681fe187b | ||
|
|
e16cab67fb | ||
|
|
50e2e0cf37 | ||
|
|
d2d20f29c1 | ||
|
|
53f4992eeb | ||
|
|
3ae1be1ca4 | ||
|
|
ce1e1b61e7 | ||
|
|
cac25ed290 | ||
|
|
8ed0b70df5 | ||
|
|
d72071c8ed | ||
|
|
ba05dfc202 | ||
|
|
f3adab5690 | ||
|
|
b1b5405809 | ||
|
|
99fcd164ac | ||
|
|
eacfacc5a2 | ||
|
|
3404b544de | ||
|
|
3604534850 | ||
|
|
2e18021e08 | ||
|
|
2e19382ccc | ||
|
|
a6dc20dc8e | ||
|
|
a1bef9146e | ||
|
|
61f18f81b7 | ||
|
|
129125faa1 | ||
|
|
b97c987f66 | ||
|
|
53e504d974 | ||
|
|
5885258c35 | ||
|
|
2034ea97d2 | ||
|
|
2cc38a85df | ||
|
|
ccd509b70c | ||
|
|
bab43d8d30 | ||
|
|
ed4761db37 | ||
|
|
db7bc4314f | ||
|
|
8a21626bf6 | ||
|
|
804f8ab085 | ||
|
|
3593990dd6 | ||
|
|
3a38a2c7c6 | ||
|
|
16aef2e7a8 | ||
|
|
bf992195e8 | ||
|
|
2fb0fd1113 | ||
|
|
709c9856c9 | ||
|
|
9e9ef3120f | ||
|
|
a7525ae6f9 | ||
|
|
5e0e91f671 | ||
|
|
09f863f3af | ||
|
|
87e8d479d8 | ||
|
|
fb8c6b9cd3 | ||
|
|
bc98b0582c | ||
|
|
84388785e7 | ||
|
|
4c519cf646 | ||
|
|
f28c049c12 | ||
|
|
33dbbcb0b5 | ||
|
|
7f6cbd137f | ||
|
|
facd8cd41f | ||
|
|
e269d8f062 | ||
|
|
dd141e4cf5 | ||
|
|
f18ee92a23 | ||
|
|
b25a88683b | ||
|
|
9dc2b8c71b | ||
|
|
2ef393531a | ||
|
|
0c1fce27a2 | ||
|
|
606cf56150 | ||
|
|
af8451e16e | ||
|
|
203a828bbd | ||
|
|
a990078884 | ||
|
|
3e5bd203f3 | ||
|
|
042979d122 | ||
|
|
11c464ed46 | ||
|
|
070054a814 | ||
|
|
5841cf5002 | ||
|
|
6dda360986 | ||
|
|
12ff14b397 | ||
|
|
20aa83e0f7 | ||
|
|
4c8c7ef46b | ||
|
|
43a6717394 | ||
|
|
b4fc57977b | ||
|
|
4f9dbdadc3 | ||
|
|
418acb052f | ||
|
|
c741297d84 | ||
|
|
55fb4df411 | ||
|
|
35643b0207 | ||
|
|
45753410df | ||
|
|
b58de541b3 | ||
|
|
3d577777bb | ||
|
|
a0e9c537a3 | ||
|
|
a2d87de7b9 | ||
|
|
032a7a4770 | ||
|
|
b2dcc59845 | ||
|
|
2ad86ee738 | ||
|
|
dcf0318bea | ||
|
|
9973ec5f6c | ||
|
|
2b43695a01 | ||
|
|
19dd39d5e6 | ||
|
|
e5eddbe7ce | ||
|
|
0f279aaf6e | ||
|
|
62cd5c776c | ||
|
|
8c8f41ac33 | ||
|
|
51880a60e9 | ||
|
|
7dffe6f502 | ||
|
|
f591e1680d | ||
|
|
0e0d7be16b | ||
|
|
8ad12646fb | ||
|
|
92ae10e338 | ||
|
|
b2b3a2e281 | ||
|
|
355e7a5ab1 | ||
|
|
6f7be71a93 | ||
|
|
4d5a4bb428 | ||
|
|
717db2486a | ||
|
|
43d450b8ae | ||
|
|
4501d129c2 | ||
|
|
d14dc599d9 | ||
|
|
18d89435e4 | ||
|
|
a1e945661b | ||
|
|
150857816d | ||
|
|
2190eec25a | ||
|
|
b6dc2b6be9 | ||
|
|
1809fa9228 | ||
|
|
864fb92bc7 | ||
|
|
43f25587d2 | ||
|
|
f08d3918dc | ||
|
|
89143bc19f | ||
|
|
7c2cac135d | ||
|
|
11fc72e763 | ||
|
|
8c4d8cef1a | ||
|
|
788d8d1f42 | ||
|
|
a59b29c731 | ||
|
|
65398ac55f | ||
|
|
8c3e70b1de | ||
|
|
8de5461759 | ||
|
|
cb20dfb547 | ||
|
|
b82cc06789 | ||
|
|
5376abc717 | ||
|
|
6826410c6a | ||
|
|
85340831cc | ||
|
|
dd4df8b734 | ||
|
|
a4a3d1bc7a | ||
|
|
4f29804602 | ||
|
|
a97cb4c61e | ||
|
|
680171bd88 | ||
|
|
e3c976406d | ||
|
|
17dda0b0fc | ||
|
|
298178b2dc | ||
|
|
3e919c0cb4 | ||
|
|
f3814e4e48 | ||
|
|
3278544d46 | ||
|
|
44cf69b093 | ||
|
|
e6344d36a7 | ||
|
|
6b879e0c75 | ||
|
|
0b5168ed23 | ||
|
|
698edf1185 | ||
|
|
737f059b3a | ||
|
|
cb8df8495b | ||
|
|
d348929ff0 | ||
|
|
c0d29d88b9 | ||
|
|
181a395515 | ||
|
|
4f7849fbc1 | ||
|
|
857ada54ea | ||
|
|
01ef1c4024 | ||
|
|
1e59b9976a | ||
|
|
7d36881b29 | ||
|
|
8378ef8437 | ||
|
|
1f83fb8a39 | ||
|
|
67612d8dd5 | ||
|
|
a42df761fb | ||
|
|
d37a9364e0 | ||
|
|
caca0abb75 | ||
|
|
b71c990c2e | ||
|
|
5948729b87 | ||
|
|
f973388879 | ||
|
|
65e0238aa2 | ||
|
|
ca088ef2e6 | ||
|
|
1a48feba72 | ||
|
|
1d728996b2 | ||
|
|
22b1ae55e5 | ||
|
|
d7f5d8dea2 | ||
|
|
39cf412938 | ||
|
|
a5ac6d594f | ||
|
|
1a74695126 | ||
|
|
35049d436e | ||
|
|
069350eaa3 | ||
|
|
e3cce71329 | ||
|
|
6bae67412a | ||
|
|
9461678a19 | ||
|
|
c22c5f6c7b | ||
|
|
e5501cfa15 | ||
|
|
14d1906320 | ||
|
|
82ca298902 | ||
|
|
27fc3b594b | ||
|
|
b4a6b3954d | ||
|
|
b6f4c9f704 | ||
|
|
ee5cec4b16 | ||
|
|
f3f7fbd0de | ||
|
|
8a344c97c9 | ||
|
|
831c73d28d | ||
|
|
bfa5027bf9 | ||
|
|
194a4a66e5 | ||
|
|
ceb66387eb | ||
|
|
b18bc04606 | ||
|
|
b7f01fc58c | ||
|
|
7fc6d9e99e | ||
|
|
232c1a5597 | ||
|
|
1414a4976e | ||
|
|
048add1dcf | ||
|
|
1ae8132be2 | ||
|
|
72cd89d5f4 | ||
|
|
b414526d02 | ||
|
|
27eb78faab | ||
|
|
6bd5223b92 | ||
|
|
e8afb4e331 | ||
|
|
390481f814 | ||
|
|
4eb0e82f75 | ||
|
|
548753aa69 | ||
|
|
cdf6068db5 | ||
|
|
6aead41e6f | ||
|
|
e48cc0257b | ||
|
|
1d05477729 | ||
|
|
2b840315b0 | ||
|
|
177be1571e | ||
|
|
b9076fc21d | ||
|
|
9dfa66ac3e | ||
|
|
b5dae56c7e | ||
|
|
4a08fdb586 | ||
|
|
b910d123eb | ||
|
|
9da07e1e54 | ||
|
|
c96d2259ce | ||
|
|
36fe905bd9 | ||
|
|
40eb5c4e1e | ||
|
|
c9b883f909 | ||
|
|
d803330e3f | ||
|
|
8f4718fb9d | ||
|
|
1162c89e77 | ||
|
|
b55190e52f | ||
|
|
74f2ab8963 | ||
|
|
4fbff8cd25 | ||
|
|
c1f58220c2 | ||
|
|
624944b82a | ||
|
|
00d81795e9 | ||
|
|
ec87c3c1f8 | ||
|
|
c1ac38d10b | ||
|
|
b7d785797f | ||
|
|
2b2b1e1539 | ||
|
|
3ae8b5ac95 | ||
|
|
05a1926ebc | ||
|
|
ce64eeeb1d | ||
|
|
6e763f3573 | ||
|
|
b448fddfe6 | ||
|
|
69eb079851 | ||
|
|
ff69fbbeb9 | ||
|
|
26fea301e0 | ||
|
|
d95e389583 | ||
|
|
72370c9c71 | ||
|
|
d04e6a53da | ||
|
|
1aa59ded15 | ||
|
|
080bd22d77 | ||
|
|
8f84132218 | ||
|
|
34771792ac | ||
|
|
17024eb0e5 | ||
|
|
da0a0f3105 | ||
|
|
98cd5a8dbc | ||
|
|
421de1013f | ||
|
|
0ee644d252 | ||
|
|
ac1bd3d07c | ||
|
|
ef4a72d383 | ||
|
|
b32b4c0691 | ||
|
|
0732cb4b8b | ||
|
|
362ee700c5 | ||
|
|
93835ea2af | ||
|
|
32400a810f | ||
|
|
17999fb96d | ||
|
|
4f8370400f | ||
|
|
09dde2b82f | ||
|
|
f6587d3299 | ||
|
|
a70b86abd0 | ||
|
|
6885082299 | ||
|
|
37c126e24d | ||
|
|
476dae8c1e | ||
|
|
c949b27468 | ||
|
|
28897e4e79 | ||
|
|
81f2c7825d | ||
|
|
5d4b56b40b | ||
|
|
dc8803d6a3 | ||
|
|
be852d0a9b | ||
|
|
c8c86f00ef | ||
|
|
13fef5ce96 | ||
|
|
69c995359b | ||
|
|
3e89638b39 | ||
|
|
8dc9b4e396 | ||
|
|
28ec8a8ec3 | ||
|
|
1b457bceb7 | ||
|
|
2c152b51b6 | ||
|
|
9771d7f53f | ||
|
|
94b7ef8a37 | ||
|
|
04c3e0d263 | ||
|
|
74c0e8804f | ||
|
|
3aa97e44e7 | ||
|
|
5df8f73b20 | ||
|
|
a9181d8a56 | ||
|
|
df68fd52c8 | ||
|
|
65f2a0afe3 | ||
|
|
6292ad5394 | ||
|
|
b8e14faefe | ||
|
|
d8a2d79240 | ||
|
|
449a048304 | ||
|
|
f347f1aed1 | ||
|
|
03ba670d36 | ||
|
|
a9847831a5 | ||
|
|
920507c163 | ||
|
|
e11dfb62d0 | ||
|
|
c68ba306fe | ||
|
|
6840ef4439 | ||
|
|
dce6f0c9a3 | ||
|
|
8fb9fb7b2d | ||
|
|
eae6fd5d91 | ||
|
|
321c7efa41 | ||
|
|
19ce3bf69b | ||
|
|
c170d6a180 | ||
|
|
80f614a54c | ||
|
|
99d8cee0db | ||
|
|
d87d6ac2ac | ||
|
|
4f4a289309 | ||
|
|
44aaf77e5d | ||
|
|
9e2d8544da | ||
|
|
8394759527 | ||
|
|
4ba432dee3 | ||
|
|
5ea439c8a0 | ||
|
|
6d33d83d20 | ||
|
|
17d3e05e16 | ||
|
|
ecc7fc9695 | ||
|
|
558fce9b47 | ||
|
|
6b0fdc881e | ||
|
|
94e55e1b5a | ||
|
|
d6e05430bf | ||
|
|
fc50dc871a | ||
|
|
7e6c3f435a | ||
|
|
9512ec7d74 | ||
|
|
202200c74a | ||
|
|
fe4e9d1a94 | ||
|
|
c330704c9f | ||
|
|
1d77852bea | ||
|
|
15a5d056ed | ||
|
|
32009c5691 | ||
|
|
41c62dc413 | ||
|
|
9c3d6fadca | ||
|
|
726542b698 | ||
|
|
51172e1c96 | ||
|
|
f8049a81e1 | ||
|
|
80653a8dad | ||
|
|
4e4816c3c8 | ||
|
|
b5dd49dbc4 | ||
|
|
1f406a391a | ||
|
|
87d2c1a7f4 | ||
|
|
ec17d119a1 | ||
|
|
8630f1365c | ||
|
|
322b08e4b2 | ||
|
|
fc3c90605c | ||
|
|
afb310ab9b | ||
|
|
580df4d361 | ||
|
|
70ccfd9802 | ||
|
|
98bc1ebd37 | ||
|
|
a9a6f1e2d4 | ||
|
|
bf49f16d7c | ||
|
|
c46e6953e1 | ||
|
|
a72f787a40 | ||
|
|
1fb21b330e | ||
|
|
af85bb91fa | ||
|
|
57bee2447e | ||
|
|
050dc8d55f | ||
|
|
4a5063c14e | ||
|
|
5adb50274e | ||
|
|
665645b510 | ||
|
|
eb87111204 | ||
|
|
739925ecc8 | ||
|
|
c8f4712e77 | ||
|
|
13f5ab7388 | ||
|
|
7d2ee607a1 | ||
|
|
68a87c775c | ||
|
|
50d344cb91 | ||
|
|
94aa547a85 | ||
|
|
dfa18b50a4 | ||
|
|
2820ee4bc5 | ||
|
|
c9ff56cc7e | ||
|
|
9ea9da8d33 | ||
|
|
297a966da3 | ||
|
|
0c5dc22a93 | ||
|
|
e81b42f8dc | ||
|
|
2a302b3f0d | ||
|
|
eb78bdcf06 | ||
|
|
e7562b6470 | ||
|
|
01e672c7e2 | ||
|
|
3c638c5ca2 | ||
|
|
5356f5aafc | ||
|
|
dafb956e6e | ||
|
|
263f669873 | ||
|
|
ceec19c6fd | ||
|
|
a513dc0c01 | ||
|
|
1948dd2420 | ||
|
|
87f9cc68a0 | ||
|
|
d0897c2e09 | ||
|
|
5e392e98b6 | ||
|
|
caf2d919b4 | ||
|
|
111630b6b7 | ||
|
|
5418865a1b | ||
|
|
752d27ee08 | ||
|
|
afa32676ac | ||
|
|
e4d3773c22 | ||
|
|
04558bc25e | ||
|
|
12b6a2602a | ||
|
|
9e493c6c4d | ||
|
|
fdebae5cc2 | ||
|
|
ff8f2472a0 | ||
|
|
f732a0edc1 | ||
|
|
d48a16598f | ||
|
|
fb5a254b83 | ||
|
|
e01f1a37e5 | ||
|
|
4887941170 | ||
|
|
d57c895127 | ||
|
|
7385a1744b | ||
|
|
dc678b37f5 | ||
|
|
cdcbd783cd | ||
|
|
20b43ec736 | ||
|
|
cb41e3314c | ||
|
|
a9edcdf4fd | ||
|
|
f5daf39bb8 | ||
|
|
aeab407dc0 | ||
|
|
d2770d7a33 | ||
|
|
ab86d0efe2 | ||
|
|
5b4d3d8038 | ||
|
|
95fffe619d | ||
|
|
ea95a819c1 | ||
|
|
44197148d0 | ||
|
|
434ded22e2 | ||
|
|
46c8398942 | ||
|
|
1369b23974 | ||
|
|
d21badb6d5 | ||
|
|
4c51d2dc28 | ||
|
|
c0b3058f7c | ||
|
|
e5eb29be05 | ||
|
|
3d3fa4980e | ||
|
|
405ed766f4 | ||
|
|
cc62387292 | ||
|
|
1594345775 | ||
|
|
2d3d5af25e | ||
|
|
c6656cffbf | ||
|
|
e4d62ebbc8 | ||
|
|
af422050b6 | ||
|
|
5f52512a5f | ||
|
|
ca0af70d53 | ||
|
|
6e0a3e0ceb | ||
|
|
24fd4e8c15 | ||
|
|
1e64a7d453 | ||
|
|
8a03c64021 | ||
|
|
f8c1df1856 | ||
|
|
5b460b7512 | ||
|
|
45277c85e0 | ||
|
|
c38069443d | ||
|
|
6da5730356 | ||
|
|
2a9f9f8e98 | ||
|
|
a334a32b35 | ||
|
|
1583e84927 | ||
|
|
8a2fef7c46 | ||
|
|
6c3f2a9871 | ||
|
|
3ac2ccbc80 | ||
|
|
a11d22d3e0 | ||
|
|
d07b9af366 | ||
|
|
1a65409d0e | ||
|
|
941c2efb52 | ||
|
|
e99a0c5975 | ||
|
|
6b8c27fdcc | ||
|
|
820260cb6c | ||
|
|
96c73f05af | ||
|
|
c674405b44 | ||
|
|
6ac82f03d8 | ||
|
|
1a206457a2 | ||
|
|
6fd407b200 | ||
|
|
d8c6be9f52 | ||
|
|
46039698ca | ||
|
|
002547fde5 | ||
|
|
547b430c91 | ||
|
|
860f05eb70 | ||
|
|
4cc2beaaca | ||
|
|
d49ab2a087 | ||
|
|
c208eec5c3 | ||
|
|
c7047b1e33 | ||
|
|
659ed48652 | ||
|
|
a9a73b635c | ||
|
|
fc484ba0dd | ||
|
|
c6ad5523c7 | ||
|
|
0a8edc2388 | ||
|
|
3e5bb54ca4 | ||
|
|
7fc8238b23 | ||
|
|
06454eddcc | ||
|
|
c93c0e9dce | ||
|
|
4ec9f947c6 | ||
|
|
6436c3657d | ||
|
|
8d1ffa4947 | ||
|
|
5d6dab779e | ||
|
|
36d1378881 | ||
|
|
187679443f | ||
|
|
97e2776480 | ||
|
|
d7ab37ad31 | ||
|
|
1f7c15628a | ||
|
|
15e8a853fb | ||
|
|
1b248721e9 | ||
|
|
fde5f7e744 | ||
|
|
f28e1e76ff | ||
|
|
4daf01e683 | ||
|
|
3752a78fa1 | ||
|
|
bbc86a15a1 | ||
|
|
9864184606 | ||
|
|
a0730475f1 | ||
|
|
d2f6350fab | ||
|
|
d535ac781e | ||
|
|
332647c296 | ||
|
|
30f0051976 | ||
|
|
1efb67b60c | ||
|
|
4c3bec2a3f | ||
|
|
39063e4bcf | ||
|
|
da67342419 | ||
|
|
50155a0838 | ||
|
|
25f773efa8 | ||
|
|
c1fcf0b075 | ||
|
|
063d5a22ba | ||
|
|
c92c1c9a3d | ||
|
|
213eb2c7c1 | ||
|
|
b284dcc7ef | ||
|
|
9822eae8ff | ||
|
|
c3dd57abc6 | ||
|
|
3c65d13c00 | ||
|
|
b84bab52e0 | ||
|
|
059bd2b54e | ||
|
|
ec29f55f53 | ||
|
|
547e0e73de | ||
|
|
d4f1660a1a | ||
|
|
6c81365ee7 | ||
|
|
f0e779c9ea | ||
|
|
71bf3815c3 | ||
|
|
d7c14201ac | ||
|
|
e963ccefe5 | ||
|
|
37252faedc | ||
|
|
5144f8ba02 | ||
|
|
16f83417fe | ||
|
|
17a506009e | ||
|
|
0b0d7784e0 | ||
|
|
151204c8d1 | ||
|
|
ced1536195 | ||
|
|
cce367b83c | ||
|
|
8101780fa1 | ||
|
|
20fcd3436c | ||
|
|
215521b800 | ||
|
|
34f8b9f82c | ||
|
|
23fd1fb35e | ||
|
|
91e362ca15 | ||
|
|
0027df28a3 | ||
|
|
9f9fcaedee | ||
|
|
a91f296562 | ||
|
|
708b9d93ff | ||
|
|
08c9c8f5fb | ||
|
|
b12b51d17a | ||
|
|
7b99aadb3f | ||
|
|
c5b551d68e | ||
|
|
fdfb7ce628 | ||
|
|
eceb7bd14b | ||
|
|
1e527c900a | ||
|
|
517f860c6b | ||
|
|
0e4da5719c | ||
|
|
7f1f9d6394 | ||
|
|
d25f723ae2 | ||
|
|
dd81ffeb00 | ||
|
|
f225ea8905 | ||
|
|
5b6999232f | ||
|
|
17d2b0da7c | ||
|
|
7768035da2 | ||
|
|
d4b53ac007 | ||
|
|
67e07c8fcd | ||
|
|
2737c63903 | ||
|
|
a367698670 | ||
|
|
eb904280e9 | ||
|
|
2ec4ad331c | ||
|
|
393c9af054 | ||
|
|
0f28e3e689 | ||
|
|
c072ff570b | ||
|
|
ec47da608f | ||
|
|
734dbfc98b | ||
|
|
a78649773f | ||
|
|
dc2429d9b7 | ||
|
|
965dabc415 | ||
|
|
b04bdb0b9f | ||
|
|
9ce304ea52 | ||
|
|
77a351c94c | ||
|
|
5d24697b0c | ||
|
|
0cbeb2fc52 | ||
|
|
934c9e23ba | ||
|
|
2441cc208f | ||
|
|
27fd8989d8 | ||
|
|
a641bd8bd1 | ||
|
|
366cb2e456 | ||
|
|
31a7e133fa | ||
|
|
c808b38778 | ||
|
|
ac04dc248c | ||
|
|
15eb927137 | ||
|
|
91ab53a471 | ||
|
|
9656d5274c | ||
|
|
01fd805ee6 | ||
|
|
846b3b81ff | ||
|
|
5c921c2bff | ||
|
|
d3317a48c0 | ||
|
|
ac8b7610cf | ||
|
|
a06edd4c4e | ||
|
|
b9d6c11e63 | ||
|
|
ce9519c9cb | ||
|
|
dbda657e6e | ||
|
|
7d2b895bfb | ||
|
|
ad719a2c07 | ||
|
|
ae31139ffe | ||
|
|
bea0806a47 | ||
|
|
7394595abb | ||
|
|
d2ce6f826a | ||
|
|
2544ff5902 | ||
|
|
113306ac23 | ||
|
|
e2cedb5594 | ||
|
|
63cf2ec616 | ||
|
|
c99cc22138 | ||
|
|
b76f7bd3ba | ||
|
|
d37e9ea1c3 | ||
|
|
1d008961ab | ||
|
|
8a7224d4b5 | ||
|
|
bd95a7fc01 | ||
|
|
6a9cdc9e18 | ||
|
|
3a9fbf88bd | ||
|
|
d9ca826095 | ||
|
|
1d36fbba53 | ||
|
|
7d230aeebc | ||
|
|
4e573c46cb | ||
|
|
0c649c3959 | ||
|
|
a14a99b681 | ||
|
|
aefd87e3e5 | ||
|
|
109d56b55e | ||
|
|
4eed0b38f8 | ||
|
|
424ae68621 | ||
|
|
101b2e7bdf | ||
|
|
049dba4b06 | ||
|
|
3024d32257 | ||
|
|
784ed7f21b | ||
|
|
2de44bfd99 | ||
|
|
65e391a4a7 | ||
|
|
16057247c2 | ||
|
|
04506c1e1e | ||
|
|
7038e2a18e | ||
|
|
3429444c0c | ||
|
|
3c46953d47 | ||
|
|
6c7a1b5697 | ||
|
|
cc35644e24 | ||
|
|
ccf2b3bc5b | ||
|
|
698a46eb7b | ||
|
|
9e5de62841 | ||
|
|
70ac9029c6 | ||
|
|
b66b989de0 | ||
|
|
9c82b2ea34 | ||
|
|
d033088113 | ||
|
|
240ccdc65e | ||
|
|
975d632007 | ||
|
|
53371344ae | ||
|
|
e9356ebe79 | ||
|
|
bc0c8fc84a | ||
|
|
6252f2c8c1 | ||
|
|
708f819182 | ||
|
|
bad4c7ed34 | ||
|
|
7e6f8f19eb | ||
|
|
a6fd969ead | ||
|
|
7b5443d680 | ||
|
|
8de723d2aa | ||
|
|
82b252ce2e | ||
|
|
a2dfb26b36 | ||
|
|
f1b6492d1c | ||
|
|
19d297f934 | ||
|
|
a23128f25f | ||
|
|
567f7c3028 | ||
|
|
30af4cfc3d | ||
|
|
019fcf519a | ||
|
|
1645562d78 | ||
|
|
fab726482c | ||
|
|
351cb9835f | ||
|
|
d0d59469bc | ||
|
|
577d934ccd | ||
|
|
4438131d56 | ||
|
|
7b13c9a9ba | ||
|
|
7d60566f3c | ||
|
|
e9a7c2f184 | ||
|
|
3e07873575 | ||
|
|
2f17584ab4 | ||
|
|
02f87b23fb | ||
|
|
31032e29f5 | ||
|
|
11bfb57809 | ||
|
|
0e83ef32bb | ||
|
|
86ae9e94cd | ||
|
|
2388250dfa | ||
|
|
d947ea65a2 | ||
|
|
4a11c04742 | ||
|
|
14b24e6050 | ||
|
|
4402d6fbd6 | ||
|
|
c69b81404c | ||
|
|
241a3efacf | ||
|
|
185d52a9d4 | ||
|
|
6b933f18dd | ||
|
|
31dbbb546c | ||
|
|
ac4f8a30e5 | ||
|
|
a060531ae3 | ||
|
|
fb77937acd | ||
|
|
9d9a81710f | ||
|
|
c3b4438d1f | ||
|
|
eeb3679b82 | ||
|
|
d2d9bfb4bd | ||
|
|
2dcbe4628b | ||
|
|
634247676a | ||
|
|
1a01b323e5 |
25
.gitattributes
vendored
Normal file
25
.gitattributes
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
* text=auto
|
||||
|
||||
# Use CRLF for certain Windows files.
|
||||
*.vcproj eol=crlf
|
||||
*.sln eol=crlf
|
||||
*.bat eol=crlf
|
||||
README-WINDOWS.txt eol=crlf
|
||||
nzbget-setup.nsi eol=crlf
|
||||
windows/package-info.json eol=crlf
|
||||
windows/resources/resource.h eol=crlf
|
||||
windows/resources/nzbget.rc eol=crlf
|
||||
|
||||
# Configure GitHub's language detector
|
||||
lib/* linguist-vendored linguist-language=C++
|
||||
webui/lib/* linguist-vendored
|
||||
Makefile.in linguist-vendored
|
||||
configure linguist-vendored
|
||||
config.sub linguist-vendored
|
||||
aclocal.m4 linguist-vendored
|
||||
config.guess linguist-vendored
|
||||
depcomp linguist-vendored
|
||||
install-sh linguist-vendored
|
||||
missing linguist-vendored
|
||||
configure.ac linguist-vendored=false
|
||||
Makefile.am linguist-vendored=false
|
||||
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
|
||||
# GNU Autotools
|
||||
.deps/
|
||||
config.h
|
||||
config.h.in~
|
||||
config.log
|
||||
config.status
|
||||
Makefile
|
||||
stamp-h1
|
||||
autom4te.cache/
|
||||
.dirstamp
|
||||
*.o-*
|
||||
|
||||
# Visual Studio User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vs/
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.ilk
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
*.sln
|
||||
.vscode/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# NZBGet specific
|
||||
nzbget
|
||||
code_revision.cpp
|
||||
*.temp
|
||||
*.pyc
|
||||
pytest.ini
|
||||
.cache
|
||||
17
.lgtm.yml
Normal file
17
.lgtm.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Configuration file for integration with http://lgtm.com
|
||||
|
||||
path_classifiers:
|
||||
library:
|
||||
# exclude these directories from default alerts report:
|
||||
- lib
|
||||
- webui/lib
|
||||
|
||||
extraction:
|
||||
cpp:
|
||||
configure:
|
||||
command:
|
||||
# compile with tests to activate scanning of C++ sources for tests
|
||||
- ./configure --enable-tests
|
||||
|
||||
queries:
|
||||
- exclude: js/incomplete-sanitization # this one gives false positives only and nothing useful
|
||||
67
.travis.yml
Normal file
67
.travis.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-5
|
||||
- unrar
|
||||
- p7zip-full
|
||||
- par2
|
||||
env:
|
||||
- COMPILER=g++-5
|
||||
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.9
|
||||
- unrar
|
||||
- p7zip-full
|
||||
- par2
|
||||
env:
|
||||
- COMPILER=g++-4.9
|
||||
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- unrar
|
||||
- p7zip-full
|
||||
- par2
|
||||
env:
|
||||
- COMPILER=g++-4.8
|
||||
- CXXFLAGS="-std=c++11 -O2 -s"
|
||||
- CONFIGUREOPTS="--disable-cpp-check"
|
||||
|
||||
- compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.6
|
||||
packages:
|
||||
- clang-3.6
|
||||
- unrar
|
||||
- p7zip-full
|
||||
- par2
|
||||
env:
|
||||
- COMPILER=clang++-3.6
|
||||
|
||||
install:
|
||||
- sudo pip install -U pytest
|
||||
|
||||
script:
|
||||
- $COMPILER --version
|
||||
- CXX=$COMPILER ./configure $CONFIGUREOPTS --enable-tests && make
|
||||
- ./nzbget --tests
|
||||
- cd tests/functional && pytest -v
|
||||
41
COPYING
41
COPYING
@@ -1,12 +1,12 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
@@ -225,7 +225,7 @@ impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
@@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names:
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
167
INSTALL
167
INSTALL
@@ -1,167 +0,0 @@
|
||||
Basic Installation
|
||||
==================
|
||||
|
||||
These are generic installation instructions.
|
||||
|
||||
The `configure' shell script attempts to guess correct values for
|
||||
various system-dependent variables used during compilation. It uses
|
||||
those values to create a `Makefile' in each directory of the package.
|
||||
It may also create one or more `.h' files containing system-dependent
|
||||
definitions. Finally, it creates a shell script `config.status' that
|
||||
you can run in the future to recreate the current configuration, a file
|
||||
`config.cache' that saves the results of its tests to speed up
|
||||
reconfiguring, and a file `config.log' containing compiler output
|
||||
(useful mainly for debugging `configure').
|
||||
|
||||
If you need to do unusual things to compile the package, please try
|
||||
to figure out how `configure' could check whether to do them, and mail
|
||||
diffs or instructions to the address given in the `README' so they can
|
||||
be considered for the next release. If at some point `config.cache'
|
||||
contains results you don't want to keep, you may remove or edit it.
|
||||
|
||||
The file `configure.in' is used to create `configure' by a program
|
||||
called `autoconf'. You only need `configure.in' if you want to change
|
||||
it or regenerate `configure' using a newer version of `autoconf'.
|
||||
|
||||
The simplest way to compile this package is:
|
||||
|
||||
1. `cd' to the directory containing the package's source code and type
|
||||
`./configure' to configure the package for your system. If you're
|
||||
using `csh' on an old version of System V, you might need to type
|
||||
`sh ./configure' instead to prevent `csh' from trying to execute
|
||||
`configure' itself.
|
||||
|
||||
Running `configure' takes a while. While running, it prints some
|
||||
messages telling which features it is checking for.
|
||||
|
||||
2. Type `make' to compile the package.
|
||||
|
||||
3. Type `make install' to install the programs and any data files and
|
||||
documentation.
|
||||
|
||||
4. You can remove the program binaries and object files from the
|
||||
source code directory by typing `make clean'.
|
||||
|
||||
Compilers and Options
|
||||
=====================
|
||||
|
||||
Some systems require unusual options for compilation or linking that
|
||||
the `configure' script does not know about. You can give `configure'
|
||||
initial values for variables by setting them in the environment. Using
|
||||
a Bourne-compatible shell, you can do that on the command line like
|
||||
this:
|
||||
CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
|
||||
|
||||
Or on systems that have the `env' program, you can do it like this:
|
||||
env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
|
||||
|
||||
Compiling For Multiple Architectures
|
||||
====================================
|
||||
|
||||
You can compile the package for more than one kind of computer at the
|
||||
same time, by placing the object files for each architecture in their
|
||||
own directory. To do this, you must use a version of `make' that
|
||||
supports the `VPATH' variable, such as GNU `make'. `cd' to the
|
||||
directory where you want the object files and executables to go and run
|
||||
the `configure' script. `configure' automatically checks for the
|
||||
source code in the directory that `configure' is in and in `..'.
|
||||
|
||||
If you have to use a `make' that does not supports the `VPATH'
|
||||
variable, you have to compile the package for one architecture at a time
|
||||
in the source code directory. After you have installed the package for
|
||||
one architecture, use `make distclean' before reconfiguring for another
|
||||
architecture.
|
||||
|
||||
Installation Names
|
||||
==================
|
||||
|
||||
By default, `make install' will install the package's files in
|
||||
`/usr/local/bin', `/usr/local/man', etc. You can specify an
|
||||
installation prefix other than `/usr/local' by giving `configure' the
|
||||
option `--prefix=PATH'.
|
||||
|
||||
You can specify separate installation prefixes for
|
||||
architecture-specific files and architecture-independent files. If you
|
||||
give `configure' the option `--exec-prefix=PATH', the package will use
|
||||
PATH as the prefix for installing programs and libraries.
|
||||
Documentation and other data files will still use the regular prefix.
|
||||
|
||||
If the package supports it, you can cause programs to be installed
|
||||
with an extra prefix or suffix on their names by giving `configure' the
|
||||
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
|
||||
|
||||
Optional Features
|
||||
=================
|
||||
|
||||
Some packages pay attention to `--enable-FEATURE' options to
|
||||
`configure', where FEATURE indicates an optional part of the package.
|
||||
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
|
||||
is something like `gnu-as' or `x' (for the X Window System). The
|
||||
`README' should mention any `--enable-' and `--with-' options that the
|
||||
package recognizes.
|
||||
|
||||
For packages that use the X Window System, `configure' can usually
|
||||
find the X include and library files automatically, but if it doesn't,
|
||||
you can use the `configure' options `--x-includes=DIR' and
|
||||
`--x-libraries=DIR' to specify their locations.
|
||||
|
||||
Specifying the System Type
|
||||
==========================
|
||||
|
||||
There may be some features `configure' can not figure out
|
||||
automatically, but needs to determine by the type of host the package
|
||||
will run on. Usually `configure' can figure that out, but if it prints
|
||||
a message saying it can not guess the host type, give it the
|
||||
`--host=TYPE' option. TYPE can either be a short name for the system
|
||||
type, such as `sun4', or a canonical name with three fields:
|
||||
CPU-COMPANY-SYSTEM
|
||||
|
||||
See the file `config.sub' for the possible values of each field. If
|
||||
`config.sub' isn't included in this package, then this package doesn't
|
||||
need to know the host type.
|
||||
|
||||
If you are building compiler tools for cross-compiling, you can also
|
||||
use the `--target=TYPE' option to select the type of system they will
|
||||
produce code for and the `--build=TYPE' option to select the type of
|
||||
system on which you are compiling the package.
|
||||
|
||||
Sharing Defaults
|
||||
================
|
||||
|
||||
If you want to set default values for `configure' scripts to share,
|
||||
you can create a site shell script called `config.site' that gives
|
||||
default values for variables like `CC', `cache_file', and `prefix'.
|
||||
`configure' looks for `PREFIX/share/config.site' if it exists, then
|
||||
`PREFIX/etc/config.site' if it exists. Or, you can set the
|
||||
`CONFIG_SITE' environment variable to the location of the site script.
|
||||
A warning: not all `configure' scripts look for a site script.
|
||||
|
||||
Operation Controls
|
||||
==================
|
||||
|
||||
`configure' recognizes the following options to control how it
|
||||
operates.
|
||||
|
||||
`--cache-file=FILE'
|
||||
Use and save the results of the tests in FILE instead of
|
||||
`./config.cache'. Set FILE to `/dev/null' to disable caching, for
|
||||
debugging `configure'.
|
||||
|
||||
`--help'
|
||||
Print a summary of the options to `configure', and exit.
|
||||
|
||||
`--quiet'
|
||||
`--silent'
|
||||
`-q'
|
||||
Do not print messages saying which checks are being made.
|
||||
|
||||
`--srcdir=DIR'
|
||||
Look for the package's source code in directory DIR. Usually
|
||||
`configure' can determine that directory automatically.
|
||||
|
||||
`--version'
|
||||
Print the version of Autoconf used to generate the `configure'
|
||||
script, and exit.
|
||||
|
||||
`configure' also accepts some other, not widely useful, options.
|
||||
|
||||
302
Makefile.am
302
Makefile.am
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# This file is part of nzbget
|
||||
# This file is part of nzbget. See <http://nzbget.net>.
|
||||
#
|
||||
# Copyright (C) 2008-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
# Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
#
|
||||
# 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
|
||||
@@ -14,9 +14,7 @@
|
||||
# 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.
|
||||
#
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
bin_PROGRAMS = nzbget
|
||||
@@ -24,10 +22,26 @@ bin_PROGRAMS = nzbget
|
||||
nzbget_SOURCES = \
|
||||
daemon/connect/Connection.cpp \
|
||||
daemon/connect/Connection.h \
|
||||
daemon/connect/TLS.cpp \
|
||||
daemon/connect/TLS.h \
|
||||
daemon/connect/TlsSocket.cpp \
|
||||
daemon/connect/TlsSocket.h \
|
||||
daemon/connect/WebDownloader.cpp \
|
||||
daemon/connect/WebDownloader.h \
|
||||
daemon/extension/FeedScript.cpp \
|
||||
daemon/extension/FeedScript.h \
|
||||
daemon/extension/CommandScript.cpp \
|
||||
daemon/extension/CommandScript.h \
|
||||
daemon/extension/NzbScript.cpp \
|
||||
daemon/extension/NzbScript.h \
|
||||
daemon/extension/PostScript.cpp \
|
||||
daemon/extension/PostScript.h \
|
||||
daemon/extension/QueueScript.cpp \
|
||||
daemon/extension/QueueScript.h \
|
||||
daemon/extension/ScanScript.cpp \
|
||||
daemon/extension/ScanScript.h \
|
||||
daemon/extension/SchedulerScript.cpp \
|
||||
daemon/extension/SchedulerScript.h \
|
||||
daemon/extension/ScriptConfig.cpp \
|
||||
daemon/extension/ScriptConfig.h \
|
||||
daemon/feed/FeedCoordinator.cpp \
|
||||
daemon/feed/FeedCoordinator.h \
|
||||
daemon/feed/FeedFile.cpp \
|
||||
@@ -44,12 +58,18 @@ nzbget_SOURCES = \
|
||||
daemon/frontend/LoggableFrontend.h \
|
||||
daemon/frontend/NCursesFrontend.cpp \
|
||||
daemon/frontend/NCursesFrontend.h \
|
||||
daemon/main/CommandLineParser.cpp \
|
||||
daemon/main/CommandLineParser.h \
|
||||
daemon/main/DiskService.cpp \
|
||||
daemon/main/DiskService.h \
|
||||
daemon/main/Maintenance.cpp \
|
||||
daemon/main/Maintenance.h \
|
||||
daemon/main/nzbget.cpp \
|
||||
daemon/main/nzbget.h \
|
||||
daemon/main/Options.cpp \
|
||||
daemon/main/Options.h \
|
||||
daemon/main/WorkState.cpp \
|
||||
daemon/main/WorkState.h \
|
||||
daemon/main/Scheduler.cpp \
|
||||
daemon/main/Scheduler.h \
|
||||
daemon/main/StackTrace.cpp \
|
||||
@@ -62,24 +82,38 @@ nzbget_SOURCES = \
|
||||
daemon/nntp/Decoder.h \
|
||||
daemon/nntp/NewsServer.cpp \
|
||||
daemon/nntp/NewsServer.h \
|
||||
daemon/nntp/NNTPConnection.cpp \
|
||||
daemon/nntp/NNTPConnection.h \
|
||||
daemon/nntp/NntpConnection.cpp \
|
||||
daemon/nntp/NntpConnection.h \
|
||||
daemon/nntp/ServerPool.cpp \
|
||||
daemon/nntp/ServerPool.h \
|
||||
daemon/nntp/StatMeter.cpp \
|
||||
daemon/nntp/StatMeter.h \
|
||||
daemon/postprocess/Cleanup.cpp \
|
||||
daemon/postprocess/Cleanup.h \
|
||||
daemon/postprocess/DupeMatcher.cpp \
|
||||
daemon/postprocess/DupeMatcher.h \
|
||||
daemon/postprocess/ParChecker.cpp \
|
||||
daemon/postprocess/ParChecker.h \
|
||||
daemon/postprocess/ParCoordinator.cpp \
|
||||
daemon/postprocess/ParCoordinator.h \
|
||||
daemon/postprocess/ParParser.cpp \
|
||||
daemon/postprocess/ParParser.h \
|
||||
daemon/postprocess/ParRenamer.cpp \
|
||||
daemon/postprocess/ParRenamer.h \
|
||||
daemon/postprocess/PostScript.cpp \
|
||||
daemon/postprocess/PostScript.h \
|
||||
daemon/postprocess/PrePostProcessor.cpp \
|
||||
daemon/postprocess/PrePostProcessor.h \
|
||||
daemon/postprocess/RarRenamer.cpp \
|
||||
daemon/postprocess/RarRenamer.h \
|
||||
daemon/postprocess/RarReader.cpp \
|
||||
daemon/postprocess/RarReader.h \
|
||||
daemon/postprocess/Rename.cpp \
|
||||
daemon/postprocess/Rename.h \
|
||||
daemon/postprocess/Repair.cpp \
|
||||
daemon/postprocess/Repair.h \
|
||||
daemon/postprocess/Unpack.cpp \
|
||||
daemon/postprocess/Unpack.h \
|
||||
daemon/postprocess/DirectUnpack.cpp \
|
||||
daemon/postprocess/DirectUnpack.h \
|
||||
daemon/queue/DirectRenamer.cpp \
|
||||
daemon/queue/DirectRenamer.h \
|
||||
daemon/queue/DiskState.cpp \
|
||||
daemon/queue/DiskState.h \
|
||||
daemon/queue/DownloadInfo.cpp \
|
||||
@@ -88,14 +122,12 @@ nzbget_SOURCES = \
|
||||
daemon/queue/DupeCoordinator.h \
|
||||
daemon/queue/HistoryCoordinator.cpp \
|
||||
daemon/queue/HistoryCoordinator.h \
|
||||
daemon/queue/NZBFile.cpp \
|
||||
daemon/queue/NZBFile.h \
|
||||
daemon/queue/NzbFile.cpp \
|
||||
daemon/queue/NzbFile.h \
|
||||
daemon/queue/QueueCoordinator.cpp \
|
||||
daemon/queue/QueueCoordinator.h \
|
||||
daemon/queue/QueueEditor.cpp \
|
||||
daemon/queue/QueueEditor.h \
|
||||
daemon/queue/QueueScript.cpp \
|
||||
daemon/queue/QueueScript.h \
|
||||
daemon/queue/Scanner.cpp \
|
||||
daemon/queue/Scanner.h \
|
||||
daemon/queue/UrlCoordinator.cpp \
|
||||
@@ -113,15 +145,32 @@ nzbget_SOURCES = \
|
||||
daemon/remote/XmlRpc.h \
|
||||
daemon/util/Log.cpp \
|
||||
daemon/util/Log.h \
|
||||
daemon/util/NString.cpp \
|
||||
daemon/util/NString.h \
|
||||
daemon/util/Container.h \
|
||||
daemon/util/Observer.cpp \
|
||||
daemon/util/Observer.h \
|
||||
daemon/util/Script.cpp \
|
||||
daemon/util/Script.h \
|
||||
daemon/util/Thread.cpp \
|
||||
daemon/util/Thread.h \
|
||||
daemon/util/Service.cpp \
|
||||
daemon/util/Service.h \
|
||||
daemon/util/FileSystem.cpp \
|
||||
daemon/util/FileSystem.h \
|
||||
daemon/util/Util.cpp \
|
||||
daemon/util/Util.h \
|
||||
svn_version.cpp
|
||||
daemon/nserv/NServMain.h \
|
||||
daemon/nserv/NServMain.cpp \
|
||||
daemon/nserv/NServFrontend.h \
|
||||
daemon/nserv/NServFrontend.cpp \
|
||||
daemon/nserv/NntpServer.h \
|
||||
daemon/nserv/NntpServer.cpp \
|
||||
daemon/nserv/NzbGenerator.h \
|
||||
daemon/nserv/NzbGenerator.cpp \
|
||||
daemon/nserv/YEncoder.h \
|
||||
daemon/nserv/YEncoder.cpp \
|
||||
code_revision.cpp
|
||||
|
||||
if WITH_PAR2
|
||||
nzbget_SOURCES += \
|
||||
@@ -149,8 +198,6 @@ nzbget_SOURCES += \
|
||||
lib/par2/md5.cpp \
|
||||
lib/par2/md5.h \
|
||||
lib/par2/par2cmdline.h \
|
||||
lib/par2/par2creatorsourcefile.cpp \
|
||||
lib/par2/par2creatorsourcefile.h \
|
||||
lib/par2/par2fileformat.cpp \
|
||||
lib/par2/par2fileformat.h \
|
||||
lib/par2/par2repairer.cpp \
|
||||
@@ -169,8 +216,28 @@ nzbget_SOURCES += \
|
||||
lib/par2/verificationpacket.h
|
||||
endif
|
||||
|
||||
# Simd decoder and Crc32
|
||||
nzbget_SOURCES += \
|
||||
lib/yencode/YEncode.h \
|
||||
lib/yencode/SimdInit.cpp \
|
||||
lib/yencode/SimdDecoder.cpp \
|
||||
lib/yencode/ScalarDecoder.cpp \
|
||||
lib/yencode/Sse2Decoder.cpp \
|
||||
lib/yencode/Ssse3Decoder.cpp \
|
||||
lib/yencode/PclmulCrc.cpp \
|
||||
lib/yencode/NeonDecoder.cpp \
|
||||
lib/yencode/AcleCrc.cpp \
|
||||
lib/yencode/SliceCrc.cpp
|
||||
|
||||
lib/yencode/Sse2Decoder.$(OBJEXT) : CXXFLAGS+=$(SSE2_CXXFLAGS)
|
||||
lib/yencode/Ssse3Decoder.$(OBJEXT) : CXXFLAGS+=$(SSSE3_CXXFLAGS)
|
||||
lib/yencode/PclmulCrc.$(OBJEXT) : CXXFLAGS+=$(PCLMUL_CXXFLAGS)
|
||||
lib/yencode/NeonDecoder.$(OBJEXT) : CXXFLAGS+=$(NEON_CXXFLAGS)
|
||||
lib/yencode/AcleCrc.$(OBJEXT) : CXXFLAGS+=$(ACLECRC_CXXFLAGS)
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-I$(srcdir)/daemon/connect \
|
||||
-I$(srcdir)/daemon/extension \
|
||||
-I$(srcdir)/daemon/feed \
|
||||
-I$(srcdir)/daemon/frontend \
|
||||
-I$(srcdir)/daemon/main \
|
||||
@@ -179,21 +246,68 @@ AM_CPPFLAGS = \
|
||||
-I$(srcdir)/daemon/queue \
|
||||
-I$(srcdir)/daemon/remote \
|
||||
-I$(srcdir)/daemon/util \
|
||||
-I$(srcdir)/lib/par2
|
||||
-I$(srcdir)/daemon/nserv \
|
||||
-I$(srcdir)/lib/par2 \
|
||||
-I$(srcdir)/lib/yencode
|
||||
|
||||
if WITH_TESTS
|
||||
nzbget_SOURCES += \
|
||||
lib/catch/catch.h \
|
||||
tests/suite/TestMain.cpp \
|
||||
tests/suite/TestMain.h \
|
||||
tests/suite/TestUtil.cpp \
|
||||
tests/suite/TestUtil.h \
|
||||
tests/main/CommandLineParserTest.cpp \
|
||||
tests/main/OptionsTest.cpp \
|
||||
tests/feed/FeedFilterTest.cpp \
|
||||
tests/postprocess/DupeMatcherTest.cpp \
|
||||
tests/postprocess/RarRenamerTest.cpp \
|
||||
tests/postprocess/RarReaderTest.cpp \
|
||||
tests/postprocess/DirectUnpackTest.cpp \
|
||||
tests/queue/NzbFileTest.cpp \
|
||||
tests/nntp/ServerPoolTest.cpp \
|
||||
tests/util/FileSystemTest.cpp \
|
||||
tests/util/NStringTest.cpp \
|
||||
tests/util/UtilTest.cpp
|
||||
|
||||
if WITH_PAR2
|
||||
nzbget_SOURCES += \
|
||||
tests/postprocess/ParCheckerTest.cpp \
|
||||
tests/postprocess/ParRenamerTest.cpp
|
||||
endif
|
||||
|
||||
AM_CPPFLAGS += \
|
||||
-I$(srcdir)/lib/catch \
|
||||
-I$(srcdir)/tests/suite
|
||||
endif
|
||||
|
||||
EXTRA_DIST = \
|
||||
Makefile.cvs \
|
||||
nzbgetd \
|
||||
$(windows_FILES) \
|
||||
$(osx_FILES)
|
||||
$(osx_FILES) \
|
||||
$(linux_FILES) \
|
||||
$(testdata_FILES) \
|
||||
$(par2doc_FILES)
|
||||
|
||||
windows_FILES = \
|
||||
daemon/windows/NTService.cpp \
|
||||
daemon/windows/NTService.h \
|
||||
daemon/windows/win32.h \
|
||||
nzbget.sln \
|
||||
nzbget.vcproj \
|
||||
nzbget-shell.bat
|
||||
daemon/windows/StdAfx.cpp \
|
||||
daemon/windows/WinService.cpp \
|
||||
daemon/windows/WinService.h \
|
||||
daemon/windows/WinConsole.cpp \
|
||||
daemon/windows/WinConsole.h \
|
||||
nzbget.vcxproj \
|
||||
windows/nzbget-command-shell.bat \
|
||||
windows/install-update.bat \
|
||||
windows/README-WINDOWS.txt \
|
||||
windows/package-info.json \
|
||||
windows/resources/mainicon.ico \
|
||||
windows/resources/nzbget.rc \
|
||||
windows/resources/resource.h \
|
||||
windows/resources/trayicon_idle.ico \
|
||||
windows/resources/trayicon_paused.ico \
|
||||
windows/resources/trayicon_working.ico \
|
||||
windows/resources/install.bmp \
|
||||
windows/resources/uninstall.bmp \
|
||||
windows/nzbget-setup.nsi
|
||||
|
||||
osx_FILES = \
|
||||
osx/App_Prefix.pch \
|
||||
@@ -219,8 +333,6 @@ osx_FILES = \
|
||||
osx/Resources/Images/mainicon.icns \
|
||||
osx/Resources/Images/statusicon.png \
|
||||
osx/Resources/Images/statusicon@2x.png \
|
||||
osx/Resources/Images/statusicon-inv.png \
|
||||
osx/Resources/Images/statusicon-inv@2x.png \
|
||||
osx/Resources/licenses/license-bootstrap.txt \
|
||||
osx/Resources/licenses/license-jquery-GPL.txt \
|
||||
osx/Resources/licenses/license-jquery-MIT.txt \
|
||||
@@ -228,10 +340,22 @@ osx_FILES = \
|
||||
osx/Resources/Localizable.strings \
|
||||
osx/Resources/Welcome.rtf
|
||||
|
||||
linux_FILES = \
|
||||
linux/installer.sh \
|
||||
linux/install-update.sh \
|
||||
linux/package-info.json \
|
||||
linux/build-info.txt \
|
||||
linux/build-nzbget \
|
||||
linux/build-unpack \
|
||||
linux/build-toolchain-android \
|
||||
linux/build-toolchain-freebsd
|
||||
|
||||
doc_FILES = \
|
||||
README \
|
||||
ChangeLog \
|
||||
COPYING \
|
||||
COPYING
|
||||
|
||||
par2doc_FILES = \
|
||||
lib/par2/AUTHORS \
|
||||
lib/par2/README
|
||||
|
||||
@@ -268,14 +392,61 @@ webui_FILES = \
|
||||
webui/img/favicon.ico \
|
||||
webui/img/download-anim-green-2x.png \
|
||||
webui/img/download-anim-orange-2x.png \
|
||||
webui/img/transmit-reload-2x.gif
|
||||
webui/img/transmit-reload-2x.gif \
|
||||
webui/img/favicon-256x256-opaque.png \
|
||||
webui/img/favicon-256x256.png
|
||||
|
||||
scripts_FILES = \
|
||||
scripts/EMail.py \
|
||||
scripts/Logger.py
|
||||
|
||||
testdata_FILES = \
|
||||
tests/testdata/dupematcher1/testfile.part01.rar \
|
||||
tests/testdata/dupematcher1/testfile.part24.rar \
|
||||
tests/testdata/dupematcher2/testfile.part04.rar \
|
||||
tests/testdata/dupematcher2/testfile.part43.rar \
|
||||
tests/testdata/nzbfile/dotless.nzb \
|
||||
tests/testdata/nzbfile/dotless.txt \
|
||||
tests/testdata/nzbfile/plain.nzb \
|
||||
tests/testdata/nzbfile/plain.txt \
|
||||
tests/testdata/parchecker/crc.txt \
|
||||
tests/testdata/parchecker/testfile.dat \
|
||||
tests/testdata/parchecker/testfile.nfo \
|
||||
tests/testdata/parchecker/testfile.par2 \
|
||||
tests/testdata/parchecker/testfile.vol00+1.PAR2 \
|
||||
tests/testdata/parchecker/testfile.vol01+2.PAR2 \
|
||||
tests/testdata/parchecker/testfile.vol03+3.PAR2 \
|
||||
tests/testdata/parchecker2/crc.txt \
|
||||
tests/testdata/parchecker2/testfile.7z.001 \
|
||||
tests/testdata/parchecker2/testfile.7z.002 \
|
||||
tests/testdata/parchecker2/testfile.7z.003 \
|
||||
tests/testdata/parchecker2/testfile.7z.par2 \
|
||||
tests/testdata/parchecker2/testfile.7z.vol0+1.PAR2 \
|
||||
tests/testdata/parchecker2/testfile.7z.vol1+2.PAR2 \
|
||||
tests/testdata/parchecker2/testfile.7z.vol3+3.PAR2 \
|
||||
tests/testdata/rarrenamer/testfile3.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile3.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile3.part03.rar \
|
||||
tests/testdata/rarrenamer/testfile5.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile5.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile5.part03.rar \
|
||||
tests/testdata/rarrenamer/testfile3oldnam.rar \
|
||||
tests/testdata/rarrenamer/testfile3oldnam.r00 \
|
||||
tests/testdata/rarrenamer/testfile3oldnam.r01 \
|
||||
tests/testdata/rarrenamer/testfile3encdata.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile3encdata.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile3encdata.part03.rar \
|
||||
tests/testdata/rarrenamer/testfile3encnam.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile3encnam.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile3encnam.part03.rar \
|
||||
tests/testdata/rarrenamer/testfile5encdata.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile5encdata.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile5encdata.part03.rar \
|
||||
tests/testdata/rarrenamer/testfile5encnam.part01.rar \
|
||||
tests/testdata/rarrenamer/testfile5encnam.part02.rar \
|
||||
tests/testdata/rarrenamer/testfile5encnam.part03.rar
|
||||
|
||||
# Install
|
||||
sbin_SCRIPTS = nzbgetd
|
||||
dist_doc_DATA = $(doc_FILES)
|
||||
exampleconfdir = $(datadir)/nzbget
|
||||
dist_exampleconf_DATA = $(exampleconf_FILES)
|
||||
@@ -293,13 +464,6 @@ nobase_dist_scripts_SCRIPTS = $(scripts_FILES)
|
||||
# 3) delete original.temp
|
||||
# These steps ensure that the output file has the same permissions as the original file.
|
||||
|
||||
# Configure installed script
|
||||
install-exec-hook:
|
||||
rm -f "$(DESTDIR)$(sbindir)/nzbgetd.temp"
|
||||
cp "$(DESTDIR)$(sbindir)/nzbgetd" "$(DESTDIR)$(sbindir)/nzbgetd.temp"
|
||||
sed 's?/usr/local/bin?$(bindir)?' < "$(DESTDIR)$(sbindir)/nzbgetd.temp" > "$(DESTDIR)$(sbindir)/nzbgetd"
|
||||
rm "$(DESTDIR)$(sbindir)/nzbgetd.temp"
|
||||
|
||||
# Prepare example configuration file
|
||||
install-data-hook:
|
||||
rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
|
||||
@@ -322,44 +486,59 @@ install-conf:
|
||||
uninstall-conf:
|
||||
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf"
|
||||
|
||||
# Determining subversion revision:
|
||||
# 1) If directory ".svn" exists we take revision from it using program svnversion (part of subversion package)
|
||||
# Determining git revision:
|
||||
# 1) If directory ".git" exists we take revision from git log.
|
||||
# File is recreated only if revision number was changed.
|
||||
# 2) If directory ".svn" doesn't exists we keep and reuse file "svn_version.cpp",
|
||||
# 2) If directory ".git" doesn't exists we keep and reuse file "code_revision.cpp",
|
||||
# which was possibly created early.
|
||||
# 3) If neither directory ".svn" nor file "svn_version.cpp" are available
|
||||
# we create new file "svn_version.c" with empty revision number.
|
||||
svn_version.cpp: FORCE
|
||||
@ if test -d ./.svn ; then \
|
||||
V="$(shell svnversion -n .)"; \
|
||||
H="$(shell test -f ./svn_version.cpp && head -n 1 svn_version.cpp)"; \
|
||||
# 3) If neither directory ".git" nor file "code_revision.cpp" are available
|
||||
# we create new file "code_revision.c" with empty revision number.
|
||||
code_revision.cpp: FORCE
|
||||
@ if test -d ./.git ; then \
|
||||
B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p'`; \
|
||||
M=`git status --porcelain` ; \
|
||||
if test "$$M" != "" ; then \
|
||||
M="M" ; \
|
||||
fi ; \
|
||||
if test "$$B" = "master" ; then \
|
||||
V="$$M" ; \
|
||||
elif test "$$B" = "develop" ; then \
|
||||
V=`git rev-list HEAD | wc -l | xargs` ; \
|
||||
V="$${V}$$M" ; \
|
||||
else \
|
||||
V=`git rev-list HEAD | wc -l | xargs` ; \
|
||||
V="$${V}$$M ($$B)" ; \
|
||||
fi ; \
|
||||
H=`test -f ./code_revision.cpp && head -n 1 code_revision.cpp`; \
|
||||
if test "/* $$V */" != "$$H" ; then \
|
||||
( \
|
||||
echo "/* $$V */" ;\
|
||||
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
|
||||
echo "const char* svn_version(void)" ;\
|
||||
echo "#include \"nzbget.h\"" ;\
|
||||
echo "const char* code_revision(void)" ;\
|
||||
echo "{" ;\
|
||||
echo " const char* SVN_Version = \"$$V\";" ;\
|
||||
echo " return SVN_Version;" ;\
|
||||
echo " const char* revision = \"$$V\";" ;\
|
||||
echo " return revision;" ;\
|
||||
echo "}" ;\
|
||||
) > svn_version.cpp ; \
|
||||
) > code_revision.cpp ; \
|
||||
fi \
|
||||
elif test -f ./svn_version.cpp ; then \
|
||||
elif test -f ./code_revision.cpp ; then \
|
||||
test "ok, reuse existing file"; \
|
||||
else \
|
||||
( \
|
||||
echo "/* */" ;\
|
||||
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
|
||||
echo "const char* svn_version(void)" ;\
|
||||
echo "#include \"nzbget.h\"" ;\
|
||||
echo "const char* code_revision(void)" ;\
|
||||
echo "{" ;\
|
||||
echo " const char* SVN_Version = \"\";" ;\
|
||||
echo " return SVN_Version;" ;\
|
||||
echo " const char* revision = \"\";" ;\
|
||||
echo " return revision;" ;\
|
||||
echo "}" ;\
|
||||
) > svn_version.cpp ; \
|
||||
) > code_revision.cpp ; \
|
||||
fi
|
||||
FORCE:
|
||||
|
||||
# Ignore "svn_version.cpp" in distcleancheck
|
||||
# Ignore "code_revision.cpp" in distcleancheck
|
||||
distcleancheck_listfiles = \
|
||||
find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \
|
||||
sh '{}' ';'
|
||||
@@ -371,4 +550,5 @@ dist-hook:
|
||||
find $(distdir)/daemon -type f -print -exec chmod -x {} \;
|
||||
find $(distdir)/webui -type f -print -exec chmod -x {} \;
|
||||
find $(distdir)/lib -type f -print -exec chmod -x {} \;
|
||||
find $(distdir)/tests -type f -print -exec chmod -x {} \;
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
default: all
|
||||
|
||||
all:
|
||||
aclocal
|
||||
autoheader
|
||||
automake
|
||||
autoconf
|
||||
|
||||
2976
Makefile.in
vendored
2976
Makefile.in
vendored
File diff suppressed because it is too large
Load Diff
48
README
48
README
@@ -44,25 +44,16 @@ depends on command-line parameters passed to the program.
|
||||
2. Supported OS
|
||||
=====================================
|
||||
|
||||
NZBGet is written in C++ and was initialy developed on Linux.
|
||||
It was ported to Windows later and tested for compatibility with
|
||||
several POSIX-OS'es.
|
||||
|
||||
It should run at least on:
|
||||
- Linux Debian 5.0 on x86;
|
||||
- Linux with uClibc on MIPSEL and ARM;
|
||||
- OpenBSD 5.0 on x86;
|
||||
- Mac OS X 10.7 Lion on x64;
|
||||
- Windows XP SP3 on x86 and Windows 7 on x64.
|
||||
NZBGet is written in C++ and works on Windows, OS X, Linux and
|
||||
most POSIX-conform OS'es.
|
||||
|
||||
Clients and servers running on different OS'es may communicate with
|
||||
each other. For example, you can use NZBGet as client on Windows to
|
||||
control your NZBGet-server running on Linux.
|
||||
|
||||
The download-section of NZBGet web-site provides binary files
|
||||
for Windows. The binary packages for many routers and NAS devices are
|
||||
also available in OPTWARE repository (http://www.nslu2-linux.org),
|
||||
but for most POSIX-systems you need to compile the program yourself.
|
||||
for Windows, OS X and Linux. For most POSIX-systems you need to compile
|
||||
the program yourself.
|
||||
|
||||
If you have downloaded binaries you can just jump to section
|
||||
"Configuration".
|
||||
@@ -71,8 +62,8 @@ If you have downloaded binaries you can just jump to section
|
||||
3. Prerequisites on POSIX
|
||||
=====================================
|
||||
|
||||
NZBGet is developed on a linux-system, but it should run on other
|
||||
POSIX platforms (see the list of tested platforms above).
|
||||
NZBGet is developed on a linux-system, but it runs on other
|
||||
POSIX platforms.
|
||||
|
||||
NZBGet absolutely needs the following libraries:
|
||||
|
||||
@@ -94,7 +85,7 @@ And the following libraries are optional:
|
||||
- for gzip support in web-server and web-client (enabled by default):
|
||||
- zlib (http://www.zlib.net)
|
||||
|
||||
All these libraries are included in modern Linux distributions and
|
||||
All these libraries are included in modern POSIX distributions and
|
||||
should be available as installable packages. Please note that you also
|
||||
need the developer packages for these libraries too, they package names
|
||||
have often suffix "dev" or "devel". On other systems you may need to
|
||||
@@ -180,8 +171,8 @@ For curses-outputmode you need ncurses or curses on your system.
|
||||
If you do not have one of them you can download and compile ncurses yourself.
|
||||
Following configure-parameters may be useful:
|
||||
|
||||
--with-libcurses-includes
|
||||
--with-libcurses-libraries
|
||||
--with-libcurses-includes=/path/to/curses/includes
|
||||
--with-libcurses-libraries=/path/to/curses/libraries
|
||||
|
||||
If you are not able to use curses or ncurses or do not want them you can
|
||||
make the program without support for curses using option "--disable-curses":
|
||||
@@ -200,11 +191,11 @@ the option --with-tlslib=(OpenSSL, GnuTLS). For example to build with GnuTLS:
|
||||
|
||||
Following configure-parameters may be useful:
|
||||
|
||||
--with-libtls-includes
|
||||
--with-libtls-libraries
|
||||
--with-libtls-includess=/path/to/gnutls/includes
|
||||
--with-libtls-libraries=/path/to/gnutls/libraries
|
||||
|
||||
--with-openssl-includes
|
||||
--with-openssl-libraries
|
||||
--with-openssl-includess=/path/to/openssl/includes
|
||||
--with-openssl-libraries=/path/to/openssl/libraries
|
||||
|
||||
If none of these libraries is available you can make the program without
|
||||
TLS/SSL support using option "--disable-tls":
|
||||
@@ -215,9 +206,8 @@ TLS/SSL support using option "--disable-tls":
|
||||
5. Compiling on Windows
|
||||
=====================================
|
||||
|
||||
NZBGet is developed using MS Visual C++ 2005. The project file and solution
|
||||
are provided. If you use MS Visual C++ 2005 Express you need to download
|
||||
and install Platform SDK.
|
||||
NZBGet is developed using MS Visual Studio 2015 (Community Edition). The project
|
||||
file is provided.
|
||||
|
||||
To compile the program with TLS/SSL support you need either OpenSSL or GnuTLS:
|
||||
- OpenSSL (http://www.openssl.org)
|
||||
@@ -253,6 +243,7 @@ The program looks for configuration file in following standard
|
||||
locations (in this order):
|
||||
|
||||
On POSIX systems:
|
||||
<EXE-DIR>/nzbget.conf
|
||||
~/.nzbget
|
||||
/etc/nzbget.conf
|
||||
/usr/etc/nzbget.conf
|
||||
@@ -465,12 +456,15 @@ Since then the program has been completely rewritten.
|
||||
NZBGet distribution archive includes additional components
|
||||
written by other authors:
|
||||
|
||||
PAR2:
|
||||
Par2:
|
||||
Peter Brian Clements <peterbclements@users.sourceforge.net>
|
||||
|
||||
PAR2 library API:
|
||||
Par2 library API:
|
||||
Francois Lesueur <flesueur@users.sourceforge.net>
|
||||
|
||||
Catch:
|
||||
Two Blue Cubes Ltd <https://github.com/philsquared/Catch>
|
||||
|
||||
jQuery:
|
||||
John Resig <http://jquery.com>
|
||||
The Dojo Foundation <http://sizzlejs.com>
|
||||
|
||||
19
README.md
Normal file
19
README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# NZBGet #
|
||||
[](http://www.gnu.org/licenses/)
|
||||
[](https://travis-ci.org/nzbget/nzbget)
|
||||
[](https://lgtm.com/projects/g/nzbget/nzbget/context:cpp)
|
||||
[](https://lgtm.com/projects/g/nzbget/nzbget/context:javascript)
|
||||
[](https://lgtm.com/projects/g/nzbget/nzbget/alerts)
|
||||
|
||||
[](https://github.com/nzbget/nzbget/releases)
|
||||
[](https://github.com/nzbget/nzbget/releases/latest)
|
||||
|
||||
NZBGet is a binary downloader, which downloads files from Usenet
|
||||
based on information given in nzb-files.
|
||||
|
||||
NZBGet is written in C++ and is known for its performance and efficiency.
|
||||
|
||||
NZBGet can run on almost any device - classic PC, NAS, media player, SAT-receiver, WLAN-router, etc.
|
||||
The download area provides precompiled binaries for Windows, macOS, Linux (compatible with
|
||||
many CPUs and platform variants), FreeBSD and Android. For other platforms
|
||||
the program can be compiled from sources.
|
||||
1220
aclocal.m4
vendored
1220
aclocal.m4
vendored
File diff suppressed because it is too large
Load Diff
83
config.h.in
83
config.h.in
@@ -3,22 +3,24 @@
|
||||
/* Define to 1 to include debug-code */
|
||||
#undef DEBUG
|
||||
|
||||
/* Define to 1 if deleting of files during reading of directory is not
|
||||
properly supported by OS */
|
||||
#undef DIRBROWSER_SNAPSHOT
|
||||
|
||||
/* Define to 1 to not use curses */
|
||||
#undef DISABLE_CURSES
|
||||
|
||||
/* Define to 1 to disable gzip-support */
|
||||
#undef DISABLE_GZIP
|
||||
|
||||
/* Define to 1 to not use libxml2, only for development purposes */
|
||||
#undef DISABLE_LIBXML2
|
||||
|
||||
/* Define to 1 to disable par-verification and repair */
|
||||
#undef DISABLE_PARCHECK
|
||||
|
||||
/* Define to 1 to not use TLS/SSL */
|
||||
#undef DISABLE_TLS
|
||||
|
||||
/* Define to 1 to enable unit and integration tests */
|
||||
#undef ENABLE_TESTS
|
||||
|
||||
/* Define to the name of macro which returns the name of function being
|
||||
compiled */
|
||||
#undef FUNCTION_MACRO_NAME
|
||||
@@ -35,16 +37,21 @@
|
||||
/* Define to 1 if you have the <curses.h> header file. */
|
||||
#undef HAVE_CURSES_H
|
||||
|
||||
/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
|
||||
*/
|
||||
#undef HAVE_DIRENT_H
|
||||
/* define if the compiler supports basic C++14 syntax */
|
||||
#undef HAVE_CXX14
|
||||
|
||||
/* Define to 1 if you have the <endian.h> header file. */
|
||||
#undef HAVE_ENDIAN_H
|
||||
|
||||
/* Define to 1 if fdatasync is supported */
|
||||
#undef HAVE_FDATASYNC
|
||||
|
||||
/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
|
||||
#undef HAVE_FSEEKO
|
||||
|
||||
/* Define to 1 if F_FULLFSYNC is supported */
|
||||
#undef HAVE_FULLFSYNC
|
||||
|
||||
/* Define to 1 if getaddrinfo is supported */
|
||||
#undef HAVE_GETADDRINFO
|
||||
|
||||
@@ -75,8 +82,8 @@
|
||||
/* Define to 1 to use GnuTLS library for TLS/SSL-support. */
|
||||
#undef HAVE_LIBGNUTLS
|
||||
|
||||
/* Define to 1 if you have the `memcpy' function. */
|
||||
#undef HAVE_MEMCPY
|
||||
/* Define to 1 if lockf is supported */
|
||||
#undef HAVE_LOCKF
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
@@ -87,39 +94,27 @@
|
||||
/* Define to 1 if you have the <ncurses/ncurses.h> header file. */
|
||||
#undef HAVE_NCURSES_NCURSES_H
|
||||
|
||||
/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
|
||||
#undef HAVE_NDIR_H
|
||||
/* Define to 1 to use Nettle library for decryption. */
|
||||
#undef HAVE_NETTLE
|
||||
|
||||
/* Define to 1 to use OpenSSL library for TLS/SSL-support. */
|
||||
/* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */
|
||||
#undef HAVE_OPENSSL
|
||||
|
||||
/* Define to 1 if pthread_cancel is supported */
|
||||
#undef HAVE_PTHREAD_CANCEL
|
||||
|
||||
/* Define to 1 if you have the <regex.h> header file. */
|
||||
#undef HAVE_REGEX_H
|
||||
|
||||
/* Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h */
|
||||
#undef HAVE_SC_NPROCESSORS_ONLN
|
||||
|
||||
/* Define to 1 if spinlocks are supported */
|
||||
#undef HAVE_SPINLOCK
|
||||
|
||||
/* Define to 1 if stdbool.h conforms to C99. */
|
||||
#undef HAVE_STDBOOL_H
|
||||
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#undef HAVE_STDINT_H
|
||||
|
||||
/* Define to 1 if you have the <stdio.h> header file. */
|
||||
#undef HAVE_STDIO_H
|
||||
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define to 1 if you have the `strcasecmp' function. */
|
||||
#undef HAVE_STRCASECMP
|
||||
|
||||
/* Define to 1 if you have the `strchr' function. */
|
||||
#undef HAVE_STRCHR
|
||||
|
||||
/* Define to 1 if you have the `stricmp' function. */
|
||||
#undef HAVE_STRICMP
|
||||
|
||||
@@ -129,14 +124,6 @@
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
#undef HAVE_STRING_H
|
||||
|
||||
/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
|
||||
*/
|
||||
#undef HAVE_SYS_DIR_H
|
||||
|
||||
/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
|
||||
*/
|
||||
#undef HAVE_SYS_NDIR_H
|
||||
|
||||
/* Define to 1 if you have the <sys/prctl.h> header file. */
|
||||
#undef HAVE_SYS_PRCTL_H
|
||||
|
||||
@@ -152,8 +139,11 @@
|
||||
/* Define to 1 if variadic macros are supported */
|
||||
#undef HAVE_VARIADIC_MACROS
|
||||
|
||||
/* Define to 1 if the system has the type `_Bool'. */
|
||||
#undef HAVE__BOOL
|
||||
/* Define to 1 if OpenSSL supports function "X509_check_host". */
|
||||
#undef HAVE_X509_CHECK_HOST
|
||||
|
||||
/* Define to 1 to exclude debug-code */
|
||||
#undef NDEBUG
|
||||
|
||||
/* Name of package */
|
||||
#undef PACKAGE
|
||||
@@ -170,6 +160,9 @@
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#undef PACKAGE_TARNAME
|
||||
|
||||
/* Define to the home page for this package. */
|
||||
#undef PACKAGE_URL
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#undef PACKAGE_VERSION
|
||||
|
||||
@@ -185,9 +178,10 @@
|
||||
/* Version number of package */
|
||||
#undef VERSION
|
||||
|
||||
/* Define to 1 if your processor stores words with the most significant byte
|
||||
first (like Motorola and SPARC, unlike Intel and VAX). */
|
||||
#undef WORDS_BIGENDIAN
|
||||
/* Enable large inode numbers on Mac OS X 10.5. */
|
||||
#ifndef _DARWIN_USE_64_BIT_INODE
|
||||
# define _DARWIN_USE_64_BIT_INODE 1
|
||||
#endif
|
||||
|
||||
/* Number of bits in a file offset, on hosts where this is settable. */
|
||||
#undef _FILE_OFFSET_BITS
|
||||
@@ -198,14 +192,5 @@
|
||||
/* Define for large files, on AIX-style hosts. */
|
||||
#undef _LARGE_FILES
|
||||
|
||||
/* Define to empty if `const' does not conform to ANSI C. */
|
||||
#undef const
|
||||
|
||||
/* Define to `__inline__' or `__inline' if that's what the C compiler
|
||||
calls it, or to nothing if 'inline' is not supported under any name. */
|
||||
#ifndef __cplusplus
|
||||
#undef inline
|
||||
#endif
|
||||
|
||||
/* Define to `unsigned int' if <sys/types.h> does not define. */
|
||||
#undef size_t
|
||||
|
||||
326
configure.ac
326
configure.ac
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# This file is part of nzbget
|
||||
# This file is part of nzbget. See <http://nzbget.net>.
|
||||
#
|
||||
# Copyright (C) 2008-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
# Copyright (C) 2008-2021 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
#
|
||||
# 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
|
||||
@@ -14,28 +14,22 @@
|
||||
# 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.
|
||||
#
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# -*- Autoconf -*-
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.59)
|
||||
AC_INIT(nzbget, 14.0, hugbug@users.sourceforge.net)
|
||||
AC_CANONICAL_SYSTEM
|
||||
AM_INIT_AUTOMAKE(nzbget, 14.0)
|
||||
AC_PREREQ(2.65)
|
||||
AC_INIT(nzbget, 21.1, hugbug@users.sourceforge.net)
|
||||
AC_CONFIG_AUX_DIR(posix)
|
||||
AC_CANONICAL_TARGET
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
AC_CONFIG_SRCDIR([daemon/main/nzbget.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AM_MAINTAINER_MODE
|
||||
|
||||
|
||||
dnl
|
||||
dnl Set default library path, if not specified in environment variable "LIBPREF".
|
||||
dnl
|
||||
if test "$LIBPREF" = ""; then
|
||||
LIBPREF="/usr"
|
||||
fi
|
||||
m4_include([posix/ax_cxx_compile_stdcxx.m4])
|
||||
|
||||
|
||||
dnl
|
||||
@@ -52,12 +46,26 @@ dnl Do all tests with c++ compiler.
|
||||
dnl
|
||||
AC_LANG(C++)
|
||||
|
||||
dnl
|
||||
dnl Determine compiler switches to support C++14 standard.
|
||||
dnl
|
||||
AC_MSG_CHECKING(whether to test compiler features)
|
||||
AC_ARG_ENABLE(cpp-check,
|
||||
[AS_HELP_STRING([--disable-cpp-check], [disable check for C++14 compiler features])],
|
||||
[ ENABLECPPCHECK=$enableval ],
|
||||
[ ENABLECPPCHECK=yes] )
|
||||
AC_MSG_RESULT($ENABLECPPCHECK)
|
||||
if test "$ENABLECPPCHECK" = "yes"; then
|
||||
AX_CXX_COMPILE_STDCXX(14,,[optional])
|
||||
if test "$HAVE_CXX14" != "1"; then
|
||||
AC_MSG_ERROR("A compiler with support for C++14 language features is required. For details visit http://nzbget.net/cpp14")
|
||||
fi
|
||||
fi
|
||||
|
||||
dnl
|
||||
dnl Checks for header files.
|
||||
dnl
|
||||
AC_CHECK_HEADERS(sys/prctl.h)
|
||||
AC_CHECK_HEADERS(regex.h)
|
||||
AC_CHECK_HEADERS(sys/prctl.h regex.h endian.h getopt.h)
|
||||
|
||||
|
||||
dnl
|
||||
@@ -69,6 +77,19 @@ AC_SEARCH_LIBS([inet_addr], [nsl])
|
||||
AC_SEARCH_LIBS([hstrerror], [resolv])
|
||||
|
||||
|
||||
dnl
|
||||
dnl Android NDK restrictions
|
||||
dnl
|
||||
AC_CHECK_FUNC(lockf,
|
||||
[AC_CHECK_DECL(lockf,
|
||||
[AC_DEFINE([HAVE_LOCKF], 1, [Define to 1 if lockf is supported])],,
|
||||
[#include <unistd.h>])])
|
||||
AC_CHECK_FUNC(pthread_cancel,
|
||||
[AC_CHECK_DECL(pthread_cancel,
|
||||
[AC_DEFINE([HAVE_PTHREAD_CANCEL], 1, [Define to 1 if pthread_cancel is supported])],,
|
||||
[#include <pthread.h>])])
|
||||
|
||||
|
||||
dnl
|
||||
dnl Getopt
|
||||
dnl
|
||||
@@ -76,6 +97,14 @@ AC_CHECK_FUNC(getopt_long,
|
||||
[AC_DEFINE([HAVE_GETOPT_LONG], 1, [Define to 1 if getopt_long is supported])],)
|
||||
|
||||
|
||||
dnl
|
||||
dnl fsync
|
||||
dnl
|
||||
AC_CHECK_FUNC(fdatasync,
|
||||
[AC_DEFINE([HAVE_FDATASYNC], 1, [Define to 1 if fdatasync is supported])],)
|
||||
AC_CHECK_DECL(F_FULLFSYNC,
|
||||
[AC_DEFINE([HAVE_FULLFSYNC], 1, [Define to 1 if F_FULLFSYNC is supported])],,[#include <fcntl.h>])
|
||||
|
||||
dnl
|
||||
dnl use 64-Bits for file sizes
|
||||
dnl
|
||||
@@ -129,7 +158,7 @@ if test "$FOUND" = "no"; then
|
||||
[ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop;
|
||||
struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ],
|
||||
AC_MSG_RESULT([[yes, and it takes 5 arguments]])
|
||||
FOUND="yes"
|
||||
FOUND="yes"
|
||||
AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]),
|
||||
FOUND="no")
|
||||
|
||||
@@ -163,14 +192,6 @@ if test "$FOUND" = "no"; then
|
||||
fi
|
||||
|
||||
|
||||
dnl
|
||||
dnl Check if spinlocks are available
|
||||
dnl
|
||||
AC_CHECK_FUNC(pthread_spin_init,
|
||||
[AC_DEFINE([HAVE_SPINLOCK], 1, [Define to 1 if spinlocks are supported])]
|
||||
AC_SEARCH_LIBS([pthread_spin_init], [pthread]),)
|
||||
|
||||
|
||||
dnl
|
||||
dnl Determine what socket length (socklen_t) data type is
|
||||
dnl
|
||||
@@ -187,32 +208,20 @@ AC_TRY_COMPILE([
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>],[
|
||||
(void)getsockopt (1, 1, 1, NULL, (size_t*)NULL)],[
|
||||
AC_MSG_RESULT(size_t)
|
||||
SOCKLEN_T=size_t],[
|
||||
AC_TRY_COMPILE([
|
||||
AC_MSG_RESULT(size_t)
|
||||
SOCKLEN_T=size_t],[
|
||||
AC_TRY_COMPILE([
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>],[
|
||||
(void)getsockopt (1, 1, 1, NULL, (int*)NULL)],[
|
||||
AC_MSG_RESULT(int)
|
||||
SOCKLEN_T=int],[
|
||||
AC_MSG_WARN(could not determine)
|
||||
SOCKLEN_T=int])])])
|
||||
AC_MSG_RESULT(int)
|
||||
SOCKLEN_T=int],[
|
||||
AC_MSG_WARN(could not determine)
|
||||
SOCKLEN_T=int])])])
|
||||
AC_DEFINE_UNQUOTED(SOCKLEN_T, $SOCKLEN_T, [Determine what socket length (socklen_t) data type is])
|
||||
|
||||
|
||||
dnl
|
||||
dnl Dir-browser's snapshot
|
||||
dnl
|
||||
AC_MSG_CHECKING(whether dir-browser snapshot workaround is needed)
|
||||
if test "$target_vendor" == "apple"; then
|
||||
AC_MSG_RESULT([[yes]])
|
||||
AC_DEFINE([DIRBROWSER_SNAPSHOT], 1, [Define to 1 if deleting of files during reading of directory is not properly supported by OS])
|
||||
else
|
||||
AC_MSG_RESULT([[no]])
|
||||
fi
|
||||
|
||||
|
||||
dnl
|
||||
dnl check cpu cores via sysconf
|
||||
dnl
|
||||
@@ -229,26 +238,36 @@ AC_TRY_COMPILE(
|
||||
dnl
|
||||
dnl checks for libxml2 includes and libraries.
|
||||
dnl
|
||||
AC_ARG_WITH(libxml2_includes,
|
||||
[AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])],
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(libxml2_libraries,
|
||||
[AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])],
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES(libxml2, libxml-2.0,
|
||||
[LIBS="${LIBS} $libxml2_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"],
|
||||
AC_MSG_CHECKING(whether to use libxml2)
|
||||
AC_ARG_ENABLE(libxml2,
|
||||
[AS_HELP_STRING([--disable-libxml2], [do not use libxml2 (removes dependency from libxml2-library, only for development purposes)])],
|
||||
[USELIBXML2=$enableval],
|
||||
[USELIBXML2=yes] )
|
||||
AC_MSG_RESULT($USELIBXML2)
|
||||
if test "$USELIBXML2" = "yes"; then
|
||||
AC_ARG_WITH(libxml2_includes,
|
||||
[AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])],
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(libxml2_libraries,
|
||||
[AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])],
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES(libxml2, libxml-2.0,
|
||||
[LIBS="${LIBS} $libxml2_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"],
|
||||
AC_MSG_ERROR("libxml2 library not found"))
|
||||
fi
|
||||
AC_CHECK_HEADER(libxml/tree.h,,
|
||||
AC_MSG_ERROR("libxml2 header files not found"))
|
||||
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
|
||||
AC_MSG_ERROR("libxml2 library not found"))
|
||||
else
|
||||
AC_DEFINE([DISABLE_LIBXML2],1,[Define to 1 to not use libxml2, only for development purposes])
|
||||
fi
|
||||
AC_CHECK_HEADER(libxml/tree.h,,
|
||||
AC_MSG_ERROR("libxml2 header files not found"))
|
||||
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
|
||||
AC_MSG_ERROR("libxml2 library not found"))
|
||||
|
||||
|
||||
dnl
|
||||
@@ -261,17 +280,23 @@ AC_ARG_ENABLE(curses,
|
||||
[USECURSES=yes] )
|
||||
AC_MSG_RESULT($USECURSES)
|
||||
if test "$USECURSES" = "yes"; then
|
||||
INCVAL="${LIBPREF}/include"
|
||||
LIBVAL="${LIBPREF}/lib"
|
||||
AC_ARG_WITH(libcurses_includes,
|
||||
[AS_HELP_STRING([--with-libcurses-includes=DIR], [libcurses include directory])],
|
||||
[INCVAL="$withval"])
|
||||
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(libcurses_libraries,
|
||||
[AS_HELP_STRING([--with-libcurses-libraries=DIR], [libcurses library directory])],
|
||||
[LIBVAL="$withval"])
|
||||
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
|
||||
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES(ncurses, ncurses,
|
||||
[LIBS="${LIBS} $ncurses_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $ncurses_CFLAGS"],
|
||||
AC_MSG_ERROR("ncurses library not found"))
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(ncurses.h,
|
||||
FOUND=yes
|
||||
AC_DEFINE([HAVE_NCURSES_H],1,[Define to 1 if you have the <ncurses.h> header file.]),
|
||||
@@ -293,6 +318,8 @@ if test "$USECURSES" = "yes"; then
|
||||
fi
|
||||
AC_SEARCH_LIBS([refresh], [ncurses curses],,
|
||||
AC_ERROR([Couldn't find curses library]))
|
||||
AC_SEARCH_LIBS([nodelay], [ncurses curses tinfo],,
|
||||
AC_ERROR([Couldn't find curses library]))
|
||||
else
|
||||
AC_DEFINE([DISABLE_CURSES],1,[Define to 1 to not use curses])
|
||||
fi
|
||||
@@ -310,21 +337,11 @@ AC_MSG_RESULT($ENABLEPARCHECK)
|
||||
if test "$ENABLEPARCHECK" = "yes"; then
|
||||
dnl PAR2 checks.
|
||||
dnl
|
||||
dnl Checks for header files.
|
||||
AC_HEADER_DIRENT
|
||||
AC_HEADER_STDBOOL
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS([stdio.h] [endian.h] [getopt.h])
|
||||
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_TYPE_SIZE_T
|
||||
AC_C_BIGENDIAN
|
||||
AC_C_CONST
|
||||
AC_C_INLINE
|
||||
AC_FUNC_FSEEKO
|
||||
dnl Checks for library functions.
|
||||
AC_FUNC_MEMCMP
|
||||
AC_CHECK_FUNCS([stricmp] [strcasecmp])
|
||||
AC_CHECK_FUNCS([strchr] [memcpy])
|
||||
AC_CHECK_FUNCS([stricmp])
|
||||
AC_CHECK_FUNCS([getopt])
|
||||
AM_CONDITIONAL(WITH_PAR2, true)
|
||||
else
|
||||
@@ -364,8 +381,7 @@ if test "$USETLS" = "yes"; then
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES([openssl], [openssl],
|
||||
[LIBS="${LIBS} $openssl_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"],
|
||||
FOUND=no)
|
||||
[CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(openssl/ssl.h,
|
||||
@@ -376,8 +392,8 @@ if test "$USETLS" = "yes"; then
|
||||
AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)])
|
||||
fi
|
||||
if test "$FOUND" = "yes"; then
|
||||
AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto],
|
||||
AC_SEARCH_LIBS([SSL_library_init], [ssl],
|
||||
AC_SEARCH_LIBS([ASN1_OBJECT_free], [crypto],
|
||||
AC_SEARCH_LIBS([SSL_CTX_new], [ssl],
|
||||
FOUND=yes,
|
||||
FOUND=no),
|
||||
FOUND=no)
|
||||
@@ -386,22 +402,29 @@ if test "$USETLS" = "yes"; then
|
||||
fi
|
||||
if test "$FOUND" = "yes"; then
|
||||
TLSLIB="OpenSSL"
|
||||
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.])
|
||||
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support and decryption.])
|
||||
AC_SEARCH_LIBS([X509_check_host], [crypto],
|
||||
AC_DEFINE([HAVE_X509_CHECK_HOST],1,[Define to 1 if OpenSSL supports function "X509_check_host".]))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then
|
||||
INCVAL="${LIBPREF}/include"
|
||||
LIBVAL="${LIBPREF}/lib"
|
||||
AC_ARG_WITH(libgnutls_includes,
|
||||
[AS_HELP_STRING([--with-libgnutls-includes=DIR], [GnuTLS include directory])],
|
||||
[INCVAL="$withval"])
|
||||
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(libgnutls_libraries,
|
||||
[AS_HELP_STRING([--with-libgnutls-libraries=DIR], [GnuTLS library directory])],
|
||||
[LIBVAL="$withval"])
|
||||
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES([gnutls], [gnutls],
|
||||
[LIBS="${LIBS} $gnutls_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $gnutls_CFLAGS"])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(gnutls/gnutls.h,
|
||||
FOUND=yes
|
||||
@@ -443,6 +466,39 @@ if test "$USETLS" = "yes"; then
|
||||
AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TLSLIB" = "GnuTLS"; then
|
||||
AC_ARG_WITH(libnettle_includes,
|
||||
[AS_HELP_STRING([--with-libnettle-includes=DIR], [Nettle include directory])],
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(libnettle_libraries,
|
||||
[AS_HELP_STRING([--with-libnettle-libraries=DIR], [Nettle library directory])],
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES([nettle], [nettle],
|
||||
[LIBS="${LIBS} $nettle_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"])
|
||||
fi
|
||||
AC_CHECK_HEADER(nettle/sha.h,
|
||||
FOUND=yes,
|
||||
FOUND=no)
|
||||
if test "$FOUND" = "no"; then
|
||||
AC_MSG_ERROR([Couldn't find Nettle headers (sha.h)])
|
||||
fi
|
||||
AC_SEARCH_LIBS([nettle_pbkdf2_hmac_sha256], [nettle],
|
||||
FOUND=yes,
|
||||
FOUND=no)
|
||||
if test "$FOUND" = "no"; then
|
||||
AC_MSG_ERROR([Couldn't find Nettle library, required when using GnuTLS])
|
||||
fi
|
||||
if test "$FOUND" = "yes"; then
|
||||
AC_DEFINE([HAVE_NETTLE],1,[Define to 1 to use Nettle library for decryption.])
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TLSLIB" = ""; then
|
||||
@@ -467,16 +523,21 @@ AC_ARG_ENABLE(gzip,
|
||||
[USEZLIB=yes] )
|
||||
AC_MSG_RESULT($USEZLIB)
|
||||
if test "$USEZLIB" = "yes"; then
|
||||
INCVAL="${LIBPREF}/include"
|
||||
LIBVAL="${LIBPREF}/lib"
|
||||
AC_ARG_WITH(zlib_includes,
|
||||
[AS_HELP_STRING([--with-zlib-includes=DIR], [zlib include directory])],
|
||||
[INCVAL="$withval"])
|
||||
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
|
||||
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
|
||||
[INCVAL="yes"],
|
||||
[INCVAL="no"])
|
||||
AC_ARG_WITH(zlib_libraries,
|
||||
[AS_HELP_STRING([--with-zlib-libraries=DIR], [zlib library directory])],
|
||||
[LIBVAL="$withval"])
|
||||
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
|
||||
[LDFLAGS="${LDFLAGS} -L${withval}"]
|
||||
[LIBVAL="yes"],
|
||||
[LIBVAL="no"])
|
||||
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
|
||||
PKG_CHECK_MODULES([zlib], [zlib],
|
||||
[LIBS="${LIBS} $zlib_LIBS"]
|
||||
[CPPFLAGS="${CPPFLAGS} $zlib_CFLAGS"])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADER(zlib.h,,
|
||||
AC_MSG_ERROR("zlib header files not found"))
|
||||
@@ -487,6 +548,36 @@ else
|
||||
fi
|
||||
|
||||
|
||||
dnl
|
||||
dnl Determine if CPU supports SIMD instructions
|
||||
dnl
|
||||
AC_MSG_CHECKING(whether to use SIMD-optimized routines)
|
||||
USE_SIMD=no
|
||||
case $host_cpu in
|
||||
i?86|x86_64)
|
||||
SSE2_CXXFLAGS="-msse2"
|
||||
SSSE3_CXXFLAGS="-mssse3"
|
||||
PCLMUL_CXXFLAGS="-msse4.1 -mpclmul"
|
||||
USE_SIMD=yes
|
||||
;;
|
||||
arm*)
|
||||
NEON_CXXFLAGS="-mfpu=neon"
|
||||
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
|
||||
USE_SIMD=yes
|
||||
;;
|
||||
aarch64)
|
||||
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
|
||||
USE_SIMD=yes
|
||||
;;
|
||||
esac
|
||||
AC_MSG_RESULT($USE_SIMD)
|
||||
AC_SUBST([SSE2_CXXFLAGS])
|
||||
AC_SUBST([SSSE3_CXXFLAGS])
|
||||
AC_SUBST([PCLMUL_CXXFLAGS])
|
||||
AC_SUBST([NEON_CXXFLAGS])
|
||||
AC_SUBST([ACLECRC_CXXFLAGS])
|
||||
|
||||
|
||||
dnl
|
||||
dnl Some Linux systems require an empty signal handler for SIGCHLD
|
||||
dnl in order for exit codes to be correctly delivered to parent process.
|
||||
@@ -524,16 +615,6 @@ dnl
|
||||
AC_DEFINE([DEBUG],1,Define to 1 to include debug-code)
|
||||
|
||||
|
||||
dnl
|
||||
dnl Set debug flags for gcc (if gcc is used)
|
||||
dnl
|
||||
if test "$CC" = "gcc"; then
|
||||
CXXFLAGS="-g -Wall"
|
||||
else
|
||||
CXXFLAGS=""
|
||||
fi
|
||||
|
||||
|
||||
dnl
|
||||
dnl check for __FUNCTION__ or __func__ macro
|
||||
dnl
|
||||
@@ -555,11 +636,10 @@ dnl
|
||||
dnl variadic macros
|
||||
dnl
|
||||
AC_MSG_CHECKING(for variadic macros)
|
||||
AC_COMPILE_IFELSE([
|
||||
#define macro(...) macrofunc(__VA_ARGS__)
|
||||
int macrofunc(int a, int b) { return a + b; }
|
||||
int test() { return macro(1, 2); }
|
||||
],
|
||||
AC_TRY_COMPILE(
|
||||
[ #define macro(...) macrofunc(__VA_ARGS__) ]
|
||||
[ int macrofunc(int a, int b) { return a + b; } ],
|
||||
[ int a=macro(1, 2); ],
|
||||
AC_MSG_RESULT([yes])
|
||||
AC_DEFINE([HAVE_VARIADIC_MACROS], 1, Define to 1 if variadic macros are supported),
|
||||
AC_MSG_RESULT([no]))
|
||||
@@ -597,8 +677,26 @@ AC_MSG_CHECKING(for rdynamic linker flag)
|
||||
dnl
|
||||
dnl End of debugging code
|
||||
dnl
|
||||
else
|
||||
AC_DEFINE([NDEBUG],1,Define to 1 to exclude debug-code)
|
||||
fi
|
||||
|
||||
|
||||
dnl
|
||||
dnl Enable test suite. Deafult: no.
|
||||
dnl
|
||||
AC_MSG_CHECKING(whether to enable unit and integration tests)
|
||||
AC_ARG_ENABLE(tests,
|
||||
[AS_HELP_STRING([--enable-tests], [enable unit and integration tests])],
|
||||
[ ENABLETESTS=$enableval ],
|
||||
[ ENABLETESTS=no] )
|
||||
AC_MSG_RESULT($ENABLETESTS)
|
||||
if test "$ENABLETESTS" = "yes"; then
|
||||
AC_DEFINE([ENABLE_TESTS],1,[Define to 1 to enable unit and integration tests])
|
||||
AM_CONDITIONAL(WITH_TESTS, true)
|
||||
else
|
||||
AM_CONDITIONAL(WITH_TESTS, false)
|
||||
fi
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,25 +15,22 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include "NString.h"
|
||||
|
||||
#ifndef HAVE_GETADDRINFO
|
||||
#ifndef HAVE_GETHOSTBYNAME_R
|
||||
#include "Thread.h"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef DISABLE_TLS
|
||||
#include "TLS.h"
|
||||
#include "TlsSocket.h"
|
||||
#endif
|
||||
|
||||
class Connection
|
||||
@@ -44,76 +41,117 @@ public:
|
||||
csConnected,
|
||||
csDisconnected,
|
||||
csListening,
|
||||
csCancelled
|
||||
csCancelled,
|
||||
csBroken
|
||||
};
|
||||
|
||||
protected:
|
||||
char* m_szHost;
|
||||
int m_iPort;
|
||||
SOCKET m_iSocket;
|
||||
bool m_bTLS;
|
||||
char* m_szCipher;
|
||||
char* m_szReadBuf;
|
||||
int m_iBufAvail;
|
||||
char* m_szBufPtr;
|
||||
EStatus m_eStatus;
|
||||
int m_iTimeout;
|
||||
bool m_bSuppressErrors;
|
||||
char m_szRemoteAddr[20];
|
||||
int m_iTotalBytesRead;
|
||||
enum EIPVersion
|
||||
{
|
||||
ipAuto,
|
||||
ipV4,
|
||||
ipV6
|
||||
};
|
||||
|
||||
Connection(const char* host, int port, bool tls);
|
||||
Connection(SOCKET socket, bool tls);
|
||||
virtual ~Connection();
|
||||
static void Init();
|
||||
static void Final();
|
||||
virtual bool Connect();
|
||||
virtual bool Disconnect();
|
||||
bool Bind();
|
||||
bool Send(const char* buffer, int size);
|
||||
bool Recv(char* buffer, int size);
|
||||
int TryRecv(char* buffer, int size);
|
||||
char* ReadLine(char* buffer, int size, int* bytesRead);
|
||||
void ReadBuffer(char** buffer, int *bufLen);
|
||||
int WriteLine(const char* buffer);
|
||||
std::unique_ptr<Connection> Accept();
|
||||
void Cancel();
|
||||
const char* GetHost() { return m_host; }
|
||||
int GetPort() { return m_port; }
|
||||
bool GetTls() { return m_tls; }
|
||||
const char* GetCipher() { return m_cipher; }
|
||||
void SetCipher(const char* cipher) { m_cipher = cipher; }
|
||||
void SetTimeout(int timeout) { m_timeout = timeout; }
|
||||
void SetIPVersion(EIPVersion ipVersion) { m_ipVersion = ipVersion; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void SetSuppressErrors(bool suppressErrors);
|
||||
bool GetSuppressErrors() { return m_suppressErrors; }
|
||||
const char* GetRemoteAddr();
|
||||
bool GetGracefull() { return m_gracefull; }
|
||||
void SetGracefull(bool gracefull) { m_gracefull = gracefull; }
|
||||
void SetForceClose(bool forceClose) { m_forceClose = forceClose; }
|
||||
#ifndef DISABLE_TLS
|
||||
TLSSocket* m_pTLSSocket;
|
||||
bool m_bTLSError;
|
||||
bool StartTls(bool isClient, const char* certFile, const char* keyFile);
|
||||
#endif
|
||||
int FetchTotalBytesRead();
|
||||
|
||||
protected:
|
||||
CString m_host;
|
||||
int m_port;
|
||||
bool m_tls;
|
||||
EIPVersion m_ipVersion = ipAuto;
|
||||
SOCKET m_socket = INVALID_SOCKET;
|
||||
CString m_cipher;
|
||||
CharBuffer m_readBuf;
|
||||
int m_bufAvail = 0;
|
||||
char* m_bufPtr = nullptr;
|
||||
EStatus m_status = csDisconnected;
|
||||
int m_timeout = 60;
|
||||
bool m_suppressErrors = true;
|
||||
BString<100> m_remoteAddr;
|
||||
int m_totalBytesRead = 0;
|
||||
bool m_gracefull = false;
|
||||
bool m_forceClose = false;
|
||||
|
||||
struct SockAddr
|
||||
{
|
||||
int ai_family;
|
||||
int ai_socktype;
|
||||
int ai_protocol;
|
||||
bool operator==(const SockAddr& rhs) const
|
||||
{ return memcmp(this, &rhs, sizeof(SockAddr)) == 0; }
|
||||
};
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
class ConTlsSocket: public TlsSocket
|
||||
{
|
||||
public:
|
||||
ConTlsSocket(SOCKET socket, bool isClient, const char* host,
|
||||
const char* certFile, const char* keyFile, const char* cipher, Connection* owner) :
|
||||
TlsSocket(socket, isClient, host, certFile, keyFile, cipher), m_owner(owner) {}
|
||||
protected:
|
||||
virtual void PrintError(const char* errMsg) { m_owner->PrintError(errMsg); }
|
||||
private:
|
||||
Connection* m_owner;
|
||||
};
|
||||
|
||||
std::unique_ptr<ConTlsSocket> m_tlsSocket;
|
||||
bool m_tlsError = false;
|
||||
#endif
|
||||
#ifndef HAVE_GETADDRINFO
|
||||
#ifndef HAVE_GETHOSTBYNAME_R
|
||||
static Mutex* m_pMutexGetHostByName;
|
||||
static std::unique_ptr<Mutex> m_getHostByNameMutex;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Connection(SOCKET iSocket, bool bTLS);
|
||||
void ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno);
|
||||
bool DoConnect();
|
||||
bool DoDisconnect();
|
||||
void ReportError(const char* msgPrefix, const char* msgArg, bool printErrCode, int errCode = 0,
|
||||
const char* errMsg = nullptr);
|
||||
virtual void PrintError(const char* errMsg);
|
||||
int GetLastNetworkError();
|
||||
bool DoConnect();
|
||||
bool DoDisconnect();
|
||||
bool InitSocketOpts(SOCKET socket);
|
||||
bool ConnectWithTimeout(void* address, int address_len);
|
||||
#ifndef HAVE_GETADDRINFO
|
||||
unsigned int ResolveHostAddr(const char* szHost);
|
||||
in_addr_t ResolveHostAddr(const char* host);
|
||||
#endif
|
||||
#ifndef DISABLE_TLS
|
||||
int recv(SOCKET s, char* buf, int len, int flags);
|
||||
int send(SOCKET s, const char* buf, int len, int flags);
|
||||
void CloseTLS();
|
||||
int recv(SOCKET s, char* buf, int len, int flags);
|
||||
int send(SOCKET s, const char* buf, int len, int flags);
|
||||
void CloseTls();
|
||||
#endif
|
||||
|
||||
public:
|
||||
Connection(const char* szHost, int iPort, bool bTLS);
|
||||
virtual ~Connection();
|
||||
static void Init();
|
||||
static void Final();
|
||||
virtual bool Connect();
|
||||
virtual bool Disconnect();
|
||||
bool Bind();
|
||||
bool Send(const char* pBuffer, int iSize);
|
||||
bool Recv(char* pBuffer, int iSize);
|
||||
int TryRecv(char* pBuffer, int iSize);
|
||||
char* ReadLine(char* pBuffer, int iSize, int* pBytesRead);
|
||||
void ReadBuffer(char** pBuffer, int *iBufLen);
|
||||
int WriteLine(const char* pBuffer);
|
||||
Connection* Accept();
|
||||
void Cancel();
|
||||
const char* GetHost() { return m_szHost; }
|
||||
int GetPort() { return m_iPort; }
|
||||
bool GetTLS() { return m_bTLS; }
|
||||
const char* GetCipher() { return m_szCipher; }
|
||||
void SetCipher(const char* szCipher);
|
||||
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void SetSuppressErrors(bool bSuppressErrors);
|
||||
bool GetSuppressErrors() { return m_bSuppressErrors; }
|
||||
const char* GetRemoteAddr();
|
||||
#ifndef DISABLE_TLS
|
||||
bool StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile);
|
||||
#endif
|
||||
int FetchTotalBytesRead();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,553 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#define SKIP_DEFAULT_WINDOWS_HEADERS
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <list>
|
||||
|
||||
#ifdef WIN32
|
||||
#include "nzbget.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#if GNUTLS_VERSION_NUMBER <= 0x020b00
|
||||
#define NEED_GCRYPT_LOCKING
|
||||
#endif
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
#include <gcrypt.h>
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
#ifndef WIN32
|
||||
#include "nzbget.h"
|
||||
#endif
|
||||
|
||||
#include "TLS.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
|
||||
/**
|
||||
* Mutexes for gcryptlib
|
||||
*/
|
||||
|
||||
typedef std::list<Mutex*> Mutexes;
|
||||
Mutexes* g_pGCryptLibMutexes;
|
||||
|
||||
static int gcry_mutex_init(void **priv)
|
||||
{
|
||||
Mutex* pMutex = new Mutex();
|
||||
g_pGCryptLibMutexes->push_back(pMutex);
|
||||
*priv = pMutex;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_destroy(void **lock)
|
||||
{
|
||||
Mutex* pMutex = ((Mutex*)*lock);
|
||||
g_pGCryptLibMutexes->remove(pMutex);
|
||||
delete pMutex;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_lock(void **lock)
|
||||
{
|
||||
((Mutex*)*lock)->Lock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_unlock(void **lock)
|
||||
{
|
||||
((Mutex*)*lock)->Unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gcry_thread_cbs gcry_threads_Mutex =
|
||||
{ GCRY_THREAD_OPTION_USER, NULL,
|
||||
gcry_mutex_init, gcry_mutex_destroy,
|
||||
gcry_mutex_lock, gcry_mutex_unlock,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
/**
|
||||
* Mutexes for OpenSSL
|
||||
*/
|
||||
|
||||
Mutex* *g_pOpenSSLMutexes;
|
||||
|
||||
static void openssl_locking(int mode, int n, const char *file, int line)
|
||||
{
|
||||
Mutex* mutex = g_pOpenSSLMutexes[n];
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
mutex->Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
mutex->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static unsigned long openssl_thread_id(void)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return (unsigned long)GetCurrentThreadId();
|
||||
#else
|
||||
return (unsigned long)pthread_self();
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
|
||||
{
|
||||
return (CRYPTO_dynlock_value*)new Mutex();
|
||||
}
|
||||
|
||||
static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line)
|
||||
{
|
||||
Mutex* mutex = (Mutex*)l;
|
||||
delete mutex;
|
||||
}
|
||||
|
||||
static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
|
||||
{
|
||||
Mutex* mutex = (Mutex*)l;
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
mutex->Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
mutex->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
|
||||
void TLSSocket::Init()
|
||||
{
|
||||
debug("Initializing TLS library");
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
g_pGCryptLibMutexes = new Mutexes();
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
|
||||
int error_code;
|
||||
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex);
|
||||
if (error_code != 0)
|
||||
{
|
||||
error("Could not initialize libcrypt");
|
||||
return;
|
||||
}
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
|
||||
error_code = gnutls_global_init();
|
||||
if (error_code != 0)
|
||||
{
|
||||
error("Could not initialize libgnutls");
|
||||
return;
|
||||
}
|
||||
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
int iMaxMutexes = CRYPTO_num_locks();
|
||||
g_pOpenSSLMutexes = (Mutex**)malloc(sizeof(Mutex*)*iMaxMutexes);
|
||||
for (int i=0; i < iMaxMutexes; i++)
|
||||
{
|
||||
g_pOpenSSLMutexes[i] = new Mutex();
|
||||
}
|
||||
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
CRYPTO_set_locking_callback(openssl_locking);
|
||||
//CRYPTO_set_id_callback(openssl_thread_id);
|
||||
CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
|
||||
CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
|
||||
CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
|
||||
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
void TLSSocket::Final()
|
||||
{
|
||||
debug("Finalizing TLS library");
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_global_deinit();
|
||||
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
// fixing memory leak in gcryptlib
|
||||
for (Mutexes::iterator it = g_pGCryptLibMutexes->begin(); it != g_pGCryptLibMutexes->end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
delete g_pGCryptLibMutexes;
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
int iMaxMutexes = CRYPTO_num_locks();
|
||||
for (int i=0; i < iMaxMutexes; i++)
|
||||
{
|
||||
delete g_pOpenSSLMutexes[i];
|
||||
}
|
||||
free(g_pOpenSSLMutexes);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
TLSSocket::TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher)
|
||||
{
|
||||
m_iSocket = iSocket;
|
||||
m_bIsClient = bIsClient;
|
||||
m_szCertFile = szCertFile ? strdup(szCertFile) : NULL;
|
||||
m_szKeyFile = szKeyFile ? strdup(szKeyFile) : NULL;
|
||||
m_szCipher = szCipher && strlen(szCipher) > 0 ? strdup(szCipher) : NULL;
|
||||
m_pContext = NULL;
|
||||
m_pSession = NULL;
|
||||
m_bSuppressErrors = false;
|
||||
m_bInitialized = false;
|
||||
m_bConnected = false;
|
||||
}
|
||||
|
||||
TLSSocket::~TLSSocket()
|
||||
{
|
||||
free(m_szCertFile);
|
||||
free(m_szKeyFile);
|
||||
free(m_szCipher);
|
||||
Close();
|
||||
}
|
||||
|
||||
void TLSSocket::ReportError(const char* szErrMsg)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
const char* errstr = gnutls_strerror(m_iRetCode);
|
||||
if (m_bSuppressErrors)
|
||||
{
|
||||
debug("%s: %s", szErrMsg, errstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("%s: %s", szErrMsg, errstr);
|
||||
}
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
int errcode;
|
||||
do
|
||||
{
|
||||
errcode = ERR_get_error();
|
||||
|
||||
char errstr[1024];
|
||||
ERR_error_string_n(errcode, errstr, sizeof(errstr));
|
||||
errstr[1024-1] = '\0';
|
||||
|
||||
if (m_bSuppressErrors)
|
||||
{
|
||||
debug("%s: %s", szErrMsg, errstr);
|
||||
}
|
||||
else if (errcode != 0)
|
||||
{
|
||||
error("%s: %s", szErrMsg, errstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("%s", szErrMsg);
|
||||
}
|
||||
} while (errcode);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
bool TLSSocket::Start()
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_certificate_credentials_t cred;
|
||||
m_iRetCode = gnutls_certificate_allocate_credentials(&cred);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("Could not create TLS context");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pContext = cred;
|
||||
|
||||
if (m_szCertFile && m_szKeyFile)
|
||||
{
|
||||
m_iRetCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_pContext,
|
||||
m_szCertFile, m_szKeyFile, GNUTLS_X509_FMT_PEM);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("Could not load certificate or key file");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_session_t sess;
|
||||
m_iRetCode = gnutls_init(&sess, m_bIsClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("Could not create TLS session");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pSession = sess;
|
||||
|
||||
m_bInitialized = true;
|
||||
|
||||
const char* szPriority = m_szCipher ? m_szCipher : "NORMAL";
|
||||
|
||||
m_iRetCode = gnutls_priority_set_direct((gnutls_session_t)m_pSession, szPriority, NULL);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("Could not select cipher for TLS session");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_iRetCode = gnutls_credentials_set((gnutls_session_t)m_pSession, GNUTLS_CRD_CERTIFICATE,
|
||||
(gnutls_certificate_credentials_t*)m_pContext);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("Could not initialize TLS session");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
gnutls_transport_set_ptr((gnutls_session_t)m_pSession, (gnutls_transport_ptr_t)(size_t)m_iSocket);
|
||||
|
||||
m_iRetCode = gnutls_handshake((gnutls_session_t)m_pSession);
|
||||
if (m_iRetCode != 0)
|
||||
{
|
||||
ReportError("TLS handshake failed");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bConnected = true;
|
||||
return true;
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
m_pContext = SSL_CTX_new(SSLv23_method());
|
||||
|
||||
if (!m_pContext)
|
||||
{
|
||||
ReportError("Could not create TLS context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_szCertFile && m_szKeyFile)
|
||||
{
|
||||
if (SSL_CTX_use_certificate_file((SSL_CTX*)m_pContext, m_szCertFile, SSL_FILETYPE_PEM) != 1)
|
||||
{
|
||||
ReportError("Could not load certificate file");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_pContext, m_szKeyFile, SSL_FILETYPE_PEM) != 1)
|
||||
{
|
||||
ReportError("Could not load key file");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_pSession = SSL_new((SSL_CTX*)m_pContext);
|
||||
if (!m_pSession)
|
||||
{
|
||||
ReportError("Could not create TLS session");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_szCipher && !SSL_set_cipher_list((SSL*)m_pSession, m_szCipher))
|
||||
{
|
||||
ReportError("Could not select cipher for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SSL_set_fd((SSL*)m_pSession, m_iSocket))
|
||||
{
|
||||
ReportError("Could not set the file descriptor for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int error_code = m_bIsClient ? SSL_connect((SSL*)m_pSession) : SSL_accept((SSL*)m_pSession);
|
||||
if (error_code < 1)
|
||||
{
|
||||
ReportError("TLS handshake failed");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bConnected = true;
|
||||
return true;
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
void TLSSocket::Close()
|
||||
{
|
||||
if (m_pSession)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
if (m_bConnected)
|
||||
{
|
||||
gnutls_bye((gnutls_session_t)m_pSession, GNUTLS_SHUT_WR);
|
||||
}
|
||||
if (m_bInitialized)
|
||||
{
|
||||
gnutls_deinit((gnutls_session_t)m_pSession);
|
||||
}
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (m_bConnected)
|
||||
{
|
||||
SSL_shutdown((SSL*)m_pSession);
|
||||
}
|
||||
SSL_free((SSL*)m_pSession);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
m_pSession = NULL;
|
||||
}
|
||||
|
||||
if (m_pContext)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_pContext);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
SSL_CTX_free((SSL_CTX*)m_pContext);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
m_pContext = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int TLSSocket::Send(const char* pBuffer, int iSize)
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
ret = gnutls_record_send((gnutls_session_t)m_pSession, pBuffer, iSize);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
ret = SSL_write((SSL*)m_pSession, pBuffer, iSize);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (ERR_peek_error() == 0)
|
||||
{
|
||||
ReportError("Could not write to TLS-Socket: Connection closed by remote host");
|
||||
}
|
||||
else
|
||||
#endif /* HAVE_OPENSSL */
|
||||
ReportError("Could not write to TLS-Socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int TLSSocket::Recv(char* pBuffer, int iSize)
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
ret = gnutls_record_recv((gnutls_session_t)m_pSession, pBuffer, iSize);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
ret = SSL_read((SSL*)m_pSession, pBuffer, iSize);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (ERR_peek_error() == 0)
|
||||
{
|
||||
ReportError("Could not read from TLS-Socket: Connection closed by remote host");
|
||||
}
|
||||
else
|
||||
#endif /* HAVE_OPENSSL */
|
||||
{
|
||||
ReportError("Could not read from TLS-Socket");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TLS_H
|
||||
#define TLS_H
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
|
||||
class TLSSocket
|
||||
{
|
||||
private:
|
||||
bool m_bIsClient;
|
||||
char* m_szCertFile;
|
||||
char* m_szKeyFile;
|
||||
char* m_szCipher;
|
||||
SOCKET m_iSocket;
|
||||
bool m_bSuppressErrors;
|
||||
int m_iRetCode;
|
||||
bool m_bInitialized;
|
||||
bool m_bConnected;
|
||||
|
||||
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TLS.h
|
||||
void* m_pContext;
|
||||
void* m_pSession;
|
||||
|
||||
void ReportError(const char* szErrMsg);
|
||||
|
||||
public:
|
||||
TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher);
|
||||
~TLSSocket();
|
||||
static void Init();
|
||||
static void Final();
|
||||
bool Start();
|
||||
void Close();
|
||||
int Send(const char* pBuffer, int iSize);
|
||||
int Recv(char* pBuffer, int iSize);
|
||||
void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; }
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
696
daemon/connect/TlsSocket.cpp
Normal file
696
daemon/connect/TlsSocket.cpp
Normal file
@@ -0,0 +1,696 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2008-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
|
||||
#include "TlsSocket.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
CString TlsSocket::m_certStore;
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
|
||||
/**
|
||||
* Mutexes for gcryptlib
|
||||
*/
|
||||
|
||||
std::vector<std::unique_ptr<Mutex>> g_GCryptLibMutexes;
|
||||
|
||||
static int gcry_mutex_init(void **priv)
|
||||
{
|
||||
g_GCryptLibMutexes.emplace_back(std::make_unique<Mutex>());
|
||||
*priv = g_GCryptLibMutexes.back().get();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_destroy(void **lock)
|
||||
{
|
||||
Mutex* mutex = ((Mutex*)*lock);
|
||||
g_GCryptLibMutexes.erase(std::find_if(g_GCryptLibMutexes.begin(), g_GCryptLibMutexes.end(),
|
||||
[mutex](std::unique_ptr<Mutex>& itMutex)
|
||||
{
|
||||
return itMutex.get() == mutex;
|
||||
}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_lock(void **lock)
|
||||
{
|
||||
((Mutex*)*lock)->Lock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gcry_mutex_unlock(void **lock)
|
||||
{
|
||||
((Mutex*)*lock)->Unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gcry_thread_cbs gcry_threads_Mutex =
|
||||
{ GCRY_THREAD_OPTION_USER, nullptr,
|
||||
gcry_mutex_init, gcry_mutex_destroy,
|
||||
gcry_mutex_lock, gcry_mutex_unlock,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
|
||||
};
|
||||
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
#ifndef CRYPTO_set_locking_callback
|
||||
#define NEED_CRYPTO_LOCKING
|
||||
#endif
|
||||
|
||||
#ifdef NEED_CRYPTO_LOCKING
|
||||
|
||||
/**
|
||||
* Mutexes for OpenSSL
|
||||
*/
|
||||
|
||||
std::vector<std::unique_ptr<Mutex>> g_OpenSSLMutexes;
|
||||
|
||||
static void openssl_locking(int mode, int n, const char* file, int line)
|
||||
{
|
||||
Mutex* mutex = g_OpenSSLMutexes[n].get();
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
mutex->Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
mutex->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
|
||||
{
|
||||
return (CRYPTO_dynlock_value*)new Mutex();
|
||||
}
|
||||
|
||||
static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line)
|
||||
{
|
||||
Mutex* mutex = (Mutex*)l;
|
||||
delete mutex;
|
||||
}
|
||||
|
||||
static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
|
||||
{
|
||||
Mutex* mutex = (Mutex*)l;
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
mutex->Lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
mutex->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* NEED_CRYPTO_LOCKING */
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
|
||||
void TlsSocket::Init()
|
||||
{
|
||||
debug("Initializing TLS library");
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
int error_code;
|
||||
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex);
|
||||
if (error_code != 0)
|
||||
{
|
||||
error("Could not initialize libcrypt");
|
||||
return;
|
||||
}
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
|
||||
error_code = gnutls_global_init();
|
||||
if (error_code != 0)
|
||||
{
|
||||
error("Could not initialize libgnutls");
|
||||
return;
|
||||
}
|
||||
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
#ifdef NEED_CRYPTO_LOCKING
|
||||
for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
|
||||
{
|
||||
g_OpenSSLMutexes.emplace_back(std::make_unique<Mutex>());
|
||||
}
|
||||
|
||||
CRYPTO_set_locking_callback(openssl_locking);
|
||||
CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
|
||||
CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
|
||||
CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
|
||||
#endif /* NEED_CRYPTO_LOCKING */
|
||||
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
void TlsSocket::Final()
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_global_deinit();
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#ifndef LIBRESSL_VERSION_NUMBER
|
||||
FIPS_mode_set(0);
|
||||
#endif
|
||||
#ifdef NEED_CRYPTO_LOCKING
|
||||
CRYPTO_set_locking_callback(nullptr);
|
||||
CRYPTO_set_id_callback(nullptr);
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
ERR_remove_state(0);
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && ! defined (LIBRESSL_VERSION_NUMBER)
|
||||
SSL_COMP_free_compression_methods();
|
||||
#endif
|
||||
//ENGINE_cleanup();
|
||||
CONF_modules_free();
|
||||
CONF_modules_unload(1);
|
||||
#ifndef OPENSSL_NO_COMP
|
||||
COMP_zlib_cleanup();
|
||||
#endif
|
||||
ERR_free_strings();
|
||||
EVP_cleanup();
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
TlsSocket::~TlsSocket()
|
||||
{
|
||||
Close();
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
ERR_remove_state(0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void TlsSocket::ReportError(const char* errMsg, bool suppressable)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
const char* errstr = gnutls_strerror(m_retCode);
|
||||
if (suppressable && m_suppressErrors)
|
||||
{
|
||||
debug("%s: %s", errMsg, errstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintError(BString<1024>("%s: %s", errMsg, errstr));
|
||||
}
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
int errcode = ERR_get_error();
|
||||
do
|
||||
{
|
||||
char errstr[1024];
|
||||
ERR_error_string_n(errcode, errstr, sizeof(errstr));
|
||||
errstr[1024-1] = '\0';
|
||||
|
||||
if (suppressable && m_suppressErrors)
|
||||
{
|
||||
debug("%s: %s", errMsg, errstr);
|
||||
}
|
||||
else if (errcode != 0)
|
||||
{
|
||||
PrintError(BString<1024>("%s: %s", errMsg, errstr));
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintError(errMsg);
|
||||
}
|
||||
|
||||
errcode = ERR_get_error();
|
||||
} while (errcode);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
void TlsSocket::PrintError(const char* errMsg)
|
||||
{
|
||||
error("%s", errMsg);
|
||||
}
|
||||
|
||||
bool TlsSocket::Start()
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_certificate_credentials_t cred;
|
||||
m_retCode = gnutls_certificate_allocate_credentials(&cred);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not create TLS context", false);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_context = cred;
|
||||
|
||||
if (m_certFile && m_keyFile)
|
||||
{
|
||||
m_retCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_context,
|
||||
m_certFile, m_keyFile, GNUTLS_X509_FMT_PEM);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not load certificate or key file", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_session_t sess;
|
||||
m_retCode = gnutls_init(&sess, m_isClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not create TLS session", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_session = sess;
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
|
||||
(m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
|
||||
|
||||
m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not select cipher for TLS", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_host)
|
||||
{
|
||||
m_retCode = gnutls_server_name_set((gnutls_session_t)m_session, GNUTLS_NAME_DNS, m_host, m_host.Length());
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not set hostname for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_retCode = gnutls_credentials_set((gnutls_session_t)m_session, GNUTLS_CRD_CERTIFICATE,
|
||||
(gnutls_certificate_credentials_t*)m_context);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError("Could not initialize TLS session", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
gnutls_transport_set_ptr((gnutls_session_t)m_session, (gnutls_transport_ptr_t)(size_t)m_socket);
|
||||
|
||||
m_retCode = gnutls_handshake((gnutls_session_t)m_session);
|
||||
if (m_retCode != 0)
|
||||
{
|
||||
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connected = true;
|
||||
return true;
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
m_context = SSL_CTX_new(SSLv23_method());
|
||||
|
||||
if (!m_context)
|
||||
{
|
||||
ReportError("Could not create TLS context", false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_certFile && m_keyFile)
|
||||
{
|
||||
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile) != 1)
|
||||
{
|
||||
ReportError("Could not load certificate file", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile, SSL_FILETYPE_PEM) != 1)
|
||||
{
|
||||
ReportError("Could not load key file", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
if (!SSL_CTX_set_options((SSL_CTX*)m_context, SSL_OP_NO_SSLv3))
|
||||
{
|
||||
ReportError("Could not select minimum protocol version for TLS", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// For ECC certificates
|
||||
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
||||
if (!ecdh)
|
||||
{
|
||||
ReportError("Could not generate ecdh parameters for TLS", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
if (!SSL_CTX_set_tmp_ecdh((SSL_CTX*)m_context, ecdh))
|
||||
{
|
||||
ReportError("Could not set ecdh parameters for TLS", false);
|
||||
EC_KEY_free(ecdh);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
EC_KEY_free(ecdh);
|
||||
}
|
||||
|
||||
if (m_isClient && !m_certStore.Empty())
|
||||
{
|
||||
// Enable certificate validation
|
||||
if (SSL_CTX_load_verify_locations((SSL_CTX*)m_context, m_certStore, nullptr) != 1)
|
||||
{
|
||||
ReportError("Could not set certificate store location", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify((SSL_CTX*)m_context, SSL_VERIFY_PEER, nullptr);
|
||||
}
|
||||
|
||||
m_session = SSL_new((SSL_CTX*)m_context);
|
||||
if (!m_session)
|
||||
{
|
||||
ReportError("Could not create TLS session", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
|
||||
{
|
||||
ReportError("Could not select cipher for TLS", false);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_isClient && m_host && !SSL_set_tlsext_host_name((SSL*)m_session, m_host))
|
||||
{
|
||||
ReportError("Could not set host name for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SSL_set_fd((SSL*)m_session, (int)m_socket))
|
||||
{
|
||||
ReportError("Could not set the file descriptor for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int error_code = m_isClient ? SSL_connect((SSL*)m_session) : SSL_accept((SSL*)m_session);
|
||||
if (error_code < 1)
|
||||
{
|
||||
long verifyRes = SSL_get_verify_result((SSL*)m_session);
|
||||
if (verifyRes != X509_V_OK)
|
||||
{
|
||||
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
|
||||
" For more info visit http://nzbget.net/certificate-verification",
|
||||
*m_host, X509_verify_cert_error_string(verifyRes)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
|
||||
}
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connected = true;
|
||||
return true;
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
bool TlsSocket::ValidateCert()
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#if GNUTLS_VERSION_NUMBER >= 0x030104
|
||||
#if GNUTLS_VERSION_NUMBER >= 0x030306
|
||||
if (FileSystem::DirectoryExists(m_certStore))
|
||||
{
|
||||
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
|
||||
{
|
||||
ReportError("Could not set certificate store location");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
|
||||
{
|
||||
ReportError("Could not set certificate store location");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int status = 0;
|
||||
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host, &status) != 0 ||
|
||||
gnutls_certificate_type_get((gnutls_session_t)m_session) != GNUTLS_CRT_X509)
|
||||
{
|
||||
ReportError("Could not verify TLS certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
if (status & GNUTLS_CERT_UNEXPECTED_OWNER)
|
||||
{
|
||||
// Extracting hostname from the certificate
|
||||
unsigned int cert_list_size = 0;
|
||||
const gnutls_datum_t* cert_list = gnutls_certificate_get_peers((gnutls_session_t)m_session, &cert_list_size);
|
||||
if (cert_list_size > 0)
|
||||
{
|
||||
gnutls_x509_crt_t cert;
|
||||
gnutls_x509_crt_init(&cert);
|
||||
gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
|
||||
char dn[256];
|
||||
size_t size = sizeof(dn);
|
||||
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn, &size) == 0)
|
||||
{
|
||||
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
|
||||
" For more info visit http://nzbget.net/certificate-verification", *m_host, dn));
|
||||
gnutls_x509_crt_deinit(cert);
|
||||
return false;
|
||||
}
|
||||
gnutls_x509_crt_deinit(cert);
|
||||
}
|
||||
}
|
||||
|
||||
gnutls_datum_t msgdata;
|
||||
if (gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, &msgdata, 0) == 0)
|
||||
{
|
||||
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
|
||||
" For more info visit http://nzbget.net/certificate-verification", *m_host, msgdata.data));
|
||||
gnutls_free(&msgdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError(BString<1024>("TLS certificate verification failed for %s."
|
||||
" For more info visit http://nzbget.net/certificate-verification", *m_host));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
// verify a server certificate was presented during the negotiation
|
||||
X509* cert = SSL_get_peer_certificate((SSL*)m_session);
|
||||
if (!cert)
|
||||
{
|
||||
PrintError(BString<1024>("TLS certificate verification failed for %s: no certificate provided by server."
|
||||
" For more info visit http://nzbget.net/certificate-verification", *m_host));
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_X509_CHECK_HOST
|
||||
// hostname verification
|
||||
if (!m_host.Empty() && X509_check_host(cert, m_host, m_host.Length(), 0, nullptr) != 1)
|
||||
{
|
||||
const unsigned char* certHost = nullptr;
|
||||
// Find the position of the CN field in the Subject field of the certificate
|
||||
int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
|
||||
if (common_name_loc >= 0)
|
||||
{
|
||||
// Extract the CN field
|
||||
X509_NAME_ENTRY* common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
|
||||
if (common_name_entry != nullptr)
|
||||
{
|
||||
// Convert the CN field to a C string
|
||||
ASN1_STRING* common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
|
||||
if (common_name_asn1 != nullptr)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
certHost = ASN1_STRING_get0_data(common_name_asn1);
|
||||
#else
|
||||
certHost = ASN1_STRING_data(common_name_asn1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
|
||||
" For more info visit http://nzbget.net/certificate-verification", *m_host, certHost));
|
||||
X509_free(cert);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
X509_free(cert);
|
||||
return true;
|
||||
#endif /* HAVE_OPENSSL */
|
||||
}
|
||||
|
||||
void TlsSocket::Close()
|
||||
{
|
||||
if (m_session)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
if (m_connected)
|
||||
{
|
||||
gnutls_bye((gnutls_session_t)m_session, GNUTLS_SHUT_WR);
|
||||
}
|
||||
if (m_initialized)
|
||||
{
|
||||
gnutls_deinit((gnutls_session_t)m_session);
|
||||
}
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (m_connected)
|
||||
{
|
||||
SSL_shutdown((SSL*)m_session);
|
||||
}
|
||||
SSL_free((SSL*)m_session);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
m_session = nullptr;
|
||||
}
|
||||
|
||||
if (m_context)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_context);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
SSL_CTX_free((SSL_CTX*)m_context);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
m_context = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int TlsSocket::Send(const char* buffer, int size)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
m_retCode = gnutls_record_send((gnutls_session_t)m_session, buffer, size);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
m_retCode = SSL_write((SSL*)m_session, buffer, size);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
if (m_retCode < 0)
|
||||
{
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (ERR_peek_error() == 0)
|
||||
{
|
||||
ReportError("Could not write to TLS-Socket: Connection closed by remote host");
|
||||
}
|
||||
else
|
||||
#endif /* HAVE_OPENSSL */
|
||||
ReportError("Could not write to TLS-Socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_retCode;
|
||||
}
|
||||
|
||||
int TlsSocket::Recv(char* buffer, int size)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
m_retCode = gnutls_record_recv((gnutls_session_t)m_session, buffer, size);
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
m_retCode = SSL_read((SSL*)m_session, buffer, size);
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
if (m_retCode < 0)
|
||||
{
|
||||
#ifdef HAVE_OPENSSL
|
||||
if (ERR_peek_error() == 0)
|
||||
{
|
||||
ReportError("Could not read from TLS-Socket: Connection closed by remote host");
|
||||
}
|
||||
else
|
||||
#endif /* HAVE_OPENSSL */
|
||||
{
|
||||
ReportError("Could not read from TLS-Socket");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_retCode;
|
||||
}
|
||||
|
||||
#endif
|
||||
69
daemon/connect/TlsSocket.h
Normal file
69
daemon/connect/TlsSocket.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2008-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TLSSOCKET_H
|
||||
#define TLSSOCKET_H
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
|
||||
#include "NString.h"
|
||||
|
||||
class TlsSocket
|
||||
{
|
||||
public:
|
||||
TlsSocket(SOCKET socket, bool isClient, const char* host,
|
||||
const char* certFile, const char* keyFile, const char* cipher) :
|
||||
m_socket(socket), m_isClient(isClient), m_host(host),
|
||||
m_certFile(certFile), m_keyFile(keyFile), m_cipher(cipher) {}
|
||||
virtual ~TlsSocket();
|
||||
static void Init();
|
||||
static void InitOptions(const char* certStore) { m_certStore = certStore; }
|
||||
static void Final();
|
||||
bool Start();
|
||||
void Close();
|
||||
int Send(const char* buffer, int size);
|
||||
int Recv(char* buffer, int size);
|
||||
void SetSuppressErrors(bool suppressErrors) { m_suppressErrors = suppressErrors; }
|
||||
|
||||
protected:
|
||||
virtual void PrintError(const char* errMsg);
|
||||
|
||||
private:
|
||||
SOCKET m_socket;
|
||||
bool m_isClient;
|
||||
CString m_host;
|
||||
CString m_certFile;
|
||||
CString m_keyFile;
|
||||
CString m_cipher;
|
||||
bool m_suppressErrors = false;
|
||||
bool m_initialized = false;
|
||||
bool m_connected = false;
|
||||
int m_retCode;
|
||||
static CString m_certStore;
|
||||
|
||||
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
|
||||
void* m_context = nullptr;
|
||||
void* m_session = nullptr;
|
||||
|
||||
void ReportError(const char* errMsg, bool suppressable = true);
|
||||
bool ValidateCert();
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2012-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2012-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,89 +14,39 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifdef WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "WebDownloader.h"
|
||||
#include "Log.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "Util.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
#include "FileSystem.h"
|
||||
|
||||
WebDownloader::WebDownloader()
|
||||
{
|
||||
debug("Creating WebDownloader");
|
||||
|
||||
m_szURL = NULL;
|
||||
m_szOutputFilename = NULL;
|
||||
m_pConnection = NULL;
|
||||
m_szInfoName = NULL;
|
||||
m_bConfirmedLength = false;
|
||||
m_eStatus = adUndefined;
|
||||
m_szOriginalFilename = NULL;
|
||||
m_bForce = false;
|
||||
m_bRetry = true;
|
||||
SetLastUpdateTimeNow();
|
||||
}
|
||||
|
||||
WebDownloader::~WebDownloader()
|
||||
void WebDownloader::SetUrl(const char* url)
|
||||
{
|
||||
debug("Destroying WebDownloader");
|
||||
|
||||
free(m_szURL);
|
||||
free(m_szInfoName);
|
||||
free(m_szOutputFilename);
|
||||
free(m_szOriginalFilename);
|
||||
m_url = WebUtil::UrlEncode(url);
|
||||
}
|
||||
|
||||
void WebDownloader::SetOutputFilename(const char* v)
|
||||
void WebDownloader::SetStatus(EStatus status)
|
||||
{
|
||||
m_szOutputFilename = strdup(v);
|
||||
m_status = status;
|
||||
Notify(nullptr);
|
||||
}
|
||||
|
||||
void WebDownloader::SetInfoName(const char* v)
|
||||
void WebDownloader::SetLastUpdateTimeNow()
|
||||
{
|
||||
m_szInfoName = strdup(v);
|
||||
}
|
||||
|
||||
void WebDownloader::SetURL(const char * szURL)
|
||||
{
|
||||
free(m_szURL);
|
||||
m_szURL = strdup(szURL);
|
||||
}
|
||||
|
||||
void WebDownloader::SetStatus(EStatus eStatus)
|
||||
{
|
||||
m_eStatus = eStatus;
|
||||
Notify(NULL);
|
||||
m_lastUpdateTime = Util::CurrentTime();
|
||||
}
|
||||
|
||||
void WebDownloader::Run()
|
||||
@@ -105,38 +55,37 @@ void WebDownloader::Run()
|
||||
|
||||
SetStatus(adRunning);
|
||||
|
||||
int iRemainedDownloadRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
|
||||
int iRemainedConnectRetries = iRemainedDownloadRetries > 10 ? iRemainedDownloadRetries : 10;
|
||||
if (!m_bRetry)
|
||||
int remainedDownloadRetries = g_Options->GetUrlRetries() > 0 ? g_Options->GetUrlRetries() : 1;
|
||||
int remainedConnectRetries = remainedDownloadRetries > 10 ? remainedDownloadRetries : 10;
|
||||
if (!m_retry)
|
||||
{
|
||||
iRemainedDownloadRetries = 1;
|
||||
iRemainedConnectRetries = 1;
|
||||
remainedDownloadRetries = 1;
|
||||
remainedConnectRetries = 1;
|
||||
}
|
||||
|
||||
m_iRedirects = 0;
|
||||
EStatus Status = adFailed;
|
||||
|
||||
while (!IsStopped() && iRemainedDownloadRetries > 0 && iRemainedConnectRetries > 0)
|
||||
while (!IsStopped() && remainedDownloadRetries > 0 && remainedConnectRetries > 0)
|
||||
{
|
||||
SetLastUpdateTimeNow();
|
||||
|
||||
Status = Download();
|
||||
Status = DownloadWithRedirects(5);
|
||||
|
||||
if ((((Status == adFailed) && (iRemainedDownloadRetries > 1)) ||
|
||||
((Status == adConnectError) && (iRemainedConnectRetries > 1)))
|
||||
&& !IsStopped() && !(!m_bForce && g_pOptions->GetPauseDownload()))
|
||||
if ((((Status == adFailed) && (remainedDownloadRetries > 1)) ||
|
||||
((Status == adConnectError) && (remainedConnectRetries > 1)))
|
||||
&& !IsStopped() && !(!m_force && g_WorkState->GetPauseDownload()))
|
||||
{
|
||||
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
|
||||
detail("Waiting %i sec to retry", g_Options->GetUrlInterval());
|
||||
int msec = 0;
|
||||
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) &&
|
||||
!(!m_bForce && g_pOptions->GetPauseDownload()))
|
||||
while (!IsStopped() && (msec < g_Options->GetUrlInterval() * 1000) &&
|
||||
!(!m_force && g_WorkState->GetPauseDownload()))
|
||||
{
|
||||
usleep(100 * 1000);
|
||||
Util::Sleep(100);
|
||||
msec += 100;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsStopped() || (!m_bForce && g_pOptions->GetPauseDownload()))
|
||||
if (IsStopped() || (!m_force && g_WorkState->GetPauseDownload()))
|
||||
{
|
||||
Status = adRetry;
|
||||
break;
|
||||
@@ -147,24 +96,13 @@ void WebDownloader::Run()
|
||||
break;
|
||||
}
|
||||
|
||||
if (Status == adRedirect)
|
||||
{
|
||||
m_iRedirects++;
|
||||
if (m_iRedirects > 5)
|
||||
{
|
||||
warn("Too many redirects for %s", m_szInfoName);
|
||||
Status = adFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Status != adConnectError)
|
||||
{
|
||||
iRemainedDownloadRetries--;
|
||||
remainedDownloadRetries--;
|
||||
}
|
||||
else
|
||||
{
|
||||
iRemainedConnectRetries--;
|
||||
remainedConnectRetries--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,17 +115,17 @@ void WebDownloader::Run()
|
||||
{
|
||||
if (IsStopped())
|
||||
{
|
||||
detail("Download %s cancelled", m_szInfoName);
|
||||
detail("Download %s cancelled", *m_infoName);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Download %s failed", m_szInfoName);
|
||||
error("Download %s failed", *m_infoName);
|
||||
}
|
||||
}
|
||||
|
||||
if (Status == adFinished)
|
||||
{
|
||||
detail("Download %s completed", m_szInfoName);
|
||||
detail("Download %s completed", *m_infoName);
|
||||
}
|
||||
|
||||
SetStatus(Status);
|
||||
@@ -199,7 +137,7 @@ WebDownloader::EStatus WebDownloader::Download()
|
||||
{
|
||||
EStatus Status = adRunning;
|
||||
|
||||
URL url(m_szURL);
|
||||
URL url(m_url);
|
||||
|
||||
Status = CreateConnection(&url);
|
||||
if (Status != adRunning)
|
||||
@@ -207,19 +145,19 @@ WebDownloader::EStatus WebDownloader::Download()
|
||||
return Status;
|
||||
}
|
||||
|
||||
m_pConnection->SetTimeout(g_pOptions->GetUrlTimeout());
|
||||
m_pConnection->SetSuppressErrors(false);
|
||||
m_connection->SetTimeout(g_Options->GetUrlTimeout());
|
||||
m_connection->SetSuppressErrors(false);
|
||||
|
||||
// connection
|
||||
bool bConnected = m_pConnection->Connect();
|
||||
if (!bConnected || IsStopped())
|
||||
bool connected = m_connection->Connect();
|
||||
if (!connected || IsStopped())
|
||||
{
|
||||
FreeConnection();
|
||||
return adConnectError;
|
||||
}
|
||||
|
||||
// Okay, we got a Connection. Now start downloading.
|
||||
detail("Downloading %s", m_szInfoName);
|
||||
detail("Downloading %s", *m_infoName);
|
||||
|
||||
SendHeaders(&url);
|
||||
|
||||
@@ -240,114 +178,122 @@ WebDownloader::EStatus WebDownloader::Download()
|
||||
if (Status != adFinished)
|
||||
{
|
||||
// Download failed, delete broken output file
|
||||
remove(m_szOutputFilename);
|
||||
FileSystem::DeleteFile(m_outputFilename);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
|
||||
WebDownloader::EStatus WebDownloader::CreateConnection(URL *pUrl)
|
||||
WebDownloader::EStatus WebDownloader::DownloadWithRedirects(int maxRedirects)
|
||||
{
|
||||
if (!pUrl->IsValid())
|
||||
// do sync download, following redirects
|
||||
EStatus status = adRedirect;
|
||||
while (status == adRedirect && maxRedirects >= 0)
|
||||
{
|
||||
error("URL is not valid: %s", pUrl->GetAddress());
|
||||
maxRedirects--;
|
||||
status = Download();
|
||||
}
|
||||
|
||||
if (status == adRedirect && maxRedirects < 0)
|
||||
{
|
||||
warn("Too many redirects for %s", *m_infoName);
|
||||
status = adFailed;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
WebDownloader::EStatus WebDownloader::CreateConnection(URL *url)
|
||||
{
|
||||
if (!url->IsValid())
|
||||
{
|
||||
error("URL is not valid: %s", url->GetAddress());
|
||||
return adFatalError;
|
||||
}
|
||||
|
||||
int iPort = pUrl->GetPort();
|
||||
if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "http"))
|
||||
int port = url->GetPort();
|
||||
if (port == 0 && !strcasecmp(url->GetProtocol(), "http"))
|
||||
{
|
||||
iPort = 80;
|
||||
port = 80;
|
||||
}
|
||||
if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "https"))
|
||||
if (port == 0 && !strcasecmp(url->GetProtocol(), "https"))
|
||||
{
|
||||
iPort = 443;
|
||||
port = 443;
|
||||
}
|
||||
|
||||
if (strcasecmp(pUrl->GetProtocol(), "http") && strcasecmp(pUrl->GetProtocol(), "https"))
|
||||
if (strcasecmp(url->GetProtocol(), "http") && strcasecmp(url->GetProtocol(), "https"))
|
||||
{
|
||||
error("Unsupported protocol in URL: %s", pUrl->GetAddress());
|
||||
error("Unsupported protocol in URL: %s", url->GetAddress());
|
||||
return adFatalError;
|
||||
}
|
||||
|
||||
#ifdef DISABLE_TLS
|
||||
if (!strcasecmp(pUrl->GetProtocol(), "https"))
|
||||
if (!strcasecmp(url->GetProtocol(), "https"))
|
||||
{
|
||||
error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", pUrl->GetAddress());
|
||||
error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", url->GetAddress());
|
||||
return adFatalError;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool bTLS = !strcasecmp(pUrl->GetProtocol(), "https");
|
||||
bool tls = !strcasecmp(url->GetProtocol(), "https");
|
||||
|
||||
m_pConnection = new Connection(pUrl->GetHost(), iPort, bTLS);
|
||||
m_connection = std::make_unique<Connection>(url->GetHost(), port, tls);
|
||||
|
||||
return adRunning;
|
||||
}
|
||||
|
||||
void WebDownloader::SendHeaders(URL *pUrl)
|
||||
void WebDownloader::SendHeaders(URL *url)
|
||||
{
|
||||
char tmp[1024];
|
||||
|
||||
// retrieve file
|
||||
snprintf(tmp, 1024, "GET %s HTTP/1.0\r\n", pUrl->GetResource());
|
||||
tmp[1024-1] = '\0';
|
||||
m_pConnection->WriteLine(tmp);
|
||||
m_connection->WriteLine(BString<1024>("GET %s HTTP/1.0\r\n", url->GetResource()));
|
||||
m_connection->WriteLine(BString<1024>("User-Agent: nzbget/%s\r\n", Util::VersionRevision()));
|
||||
|
||||
snprintf(tmp, 1024, "User-Agent: nzbget/%s\r\n", Util::VersionRevision());
|
||||
tmp[1024-1] = '\0';
|
||||
m_pConnection->WriteLine(tmp);
|
||||
|
||||
if ((!strcasecmp(pUrl->GetProtocol(), "http") && (pUrl->GetPort() == 80 || pUrl->GetPort() == 0)) ||
|
||||
(!strcasecmp(pUrl->GetProtocol(), "https") && (pUrl->GetPort() == 443 || pUrl->GetPort() == 0)))
|
||||
if ((!strcasecmp(url->GetProtocol(), "http") && (url->GetPort() == 80 || url->GetPort() == 0)) ||
|
||||
(!strcasecmp(url->GetProtocol(), "https") && (url->GetPort() == 443 || url->GetPort() == 0)))
|
||||
{
|
||||
snprintf(tmp, 1024, "Host: %s\r\n", pUrl->GetHost());
|
||||
m_connection->WriteLine(BString<1024>("Host: %s\r\n", url->GetHost()));
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(tmp, 1024, "Host: %s:%i\r\n", pUrl->GetHost(), pUrl->GetPort());
|
||||
m_connection->WriteLine(BString<1024>("Host: %s:%i\r\n", url->GetHost(), url->GetPort()));
|
||||
}
|
||||
tmp[1024-1] = '\0';
|
||||
m_pConnection->WriteLine(tmp);
|
||||
|
||||
m_pConnection->WriteLine("Accept: */*\r\n");
|
||||
m_connection->WriteLine("Accept: */*\r\n");
|
||||
#ifndef DISABLE_GZIP
|
||||
m_pConnection->WriteLine("Accept-Encoding: gzip\r\n");
|
||||
m_connection->WriteLine("Accept-Encoding: gzip\r\n");
|
||||
#endif
|
||||
m_pConnection->WriteLine("Connection: close\r\n");
|
||||
m_pConnection->WriteLine("\r\n");
|
||||
m_connection->WriteLine("Connection: close\r\n");
|
||||
m_connection->WriteLine("\r\n");
|
||||
}
|
||||
|
||||
WebDownloader::EStatus WebDownloader::DownloadHeaders()
|
||||
{
|
||||
EStatus Status = adRunning;
|
||||
|
||||
m_bConfirmedLength = false;
|
||||
const int LineBufSize = 1024*10;
|
||||
char* szLineBuf = (char*)malloc(LineBufSize);
|
||||
m_iContentLen = -1;
|
||||
bool bFirstLine = true;
|
||||
m_bGZip = false;
|
||||
m_bRedirecting = false;
|
||||
m_bRedirected = false;
|
||||
m_confirmedLength = false;
|
||||
CharBuffer lineBuf(1024*10);
|
||||
m_contentLen = -1;
|
||||
bool firstLine = true;
|
||||
m_gzip = false;
|
||||
m_redirecting = false;
|
||||
m_redirected = false;
|
||||
|
||||
// Headers
|
||||
while (!IsStopped())
|
||||
{
|
||||
SetLastUpdateTimeNow();
|
||||
|
||||
int iLen = 0;
|
||||
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
|
||||
int len = 0;
|
||||
char* line = m_connection->ReadLine(lineBuf, lineBuf.Size(), &len);
|
||||
|
||||
if (bFirstLine)
|
||||
if (firstLine)
|
||||
{
|
||||
Status = CheckResponse(szLineBuf);
|
||||
Status = CheckResponse(lineBuf);
|
||||
if (Status != adRunning)
|
||||
{
|
||||
break;
|
||||
}
|
||||
bFirstLine = false;
|
||||
firstLine = false;
|
||||
}
|
||||
|
||||
// Have we encountered a timeout?
|
||||
@@ -355,7 +301,7 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
|
||||
{
|
||||
if (!IsStopped())
|
||||
{
|
||||
warn("URL %s failed: Unexpected end of file", m_szInfoName);
|
||||
warn("URL %s failed: Unexpected end of file", *m_infoName);
|
||||
}
|
||||
Status = adFailed;
|
||||
break;
|
||||
@@ -366,25 +312,19 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
|
||||
// detect body of response
|
||||
if (*line == '\r' || *line == '\n')
|
||||
{
|
||||
if (m_iContentLen == -1 && !m_bGZip)
|
||||
{
|
||||
warn("URL %s: Content-Length is not submitted by server, cannot verify whether the file is complete", m_szInfoName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Util::TrimRight(line);
|
||||
ProcessHeader(line);
|
||||
|
||||
if (m_bRedirected)
|
||||
if (m_redirected)
|
||||
{
|
||||
Status = adRedirect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(szLineBuf);
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
@@ -392,17 +332,16 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
|
||||
{
|
||||
EStatus Status = adRunning;
|
||||
|
||||
m_pOutFile = NULL;
|
||||
bool bEnd = false;
|
||||
const int LineBufSize = 1024*10;
|
||||
char* szLineBuf = (char*)malloc(LineBufSize);
|
||||
int iWrittenLen = 0;
|
||||
m_outFile.Close();
|
||||
bool end = false;
|
||||
CharBuffer lineBuf(1024*10);
|
||||
int writtenLen = 0;
|
||||
|
||||
#ifndef DISABLE_GZIP
|
||||
m_pGUnzipStream = NULL;
|
||||
if (m_bGZip)
|
||||
m_gUnzipStream.reset();
|
||||
if (m_gzip)
|
||||
{
|
||||
m_pGUnzipStream = new GUnzipStream(1024*10);
|
||||
m_gUnzipStream = std::make_unique<GUnzipStream>(1024*10);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -411,66 +350,61 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
|
||||
{
|
||||
SetLastUpdateTimeNow();
|
||||
|
||||
char* szBuffer;
|
||||
int iLen;
|
||||
m_pConnection->ReadBuffer(&szBuffer, &iLen);
|
||||
if (iLen == 0)
|
||||
char* buffer;
|
||||
int len;
|
||||
m_connection->ReadBuffer(&buffer, &len);
|
||||
if (len == 0)
|
||||
{
|
||||
iLen = m_pConnection->TryRecv(szLineBuf, LineBufSize);
|
||||
szBuffer = szLineBuf;
|
||||
len = m_connection->TryRecv(lineBuf, lineBuf.Size());
|
||||
buffer = lineBuf;
|
||||
}
|
||||
|
||||
// Have we encountered a timeout?
|
||||
if (iLen <= 0)
|
||||
// Connection closed or timeout?
|
||||
if (len <= 0)
|
||||
{
|
||||
if (m_iContentLen == -1 && iWrittenLen > 0)
|
||||
if (len == 0 && m_contentLen == -1 && writtenLen > 0)
|
||||
{
|
||||
bEnd = true;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!IsStopped())
|
||||
{
|
||||
warn("URL %s failed: Unexpected end of file", m_szInfoName);
|
||||
warn("URL %s failed: Unexpected end of file", *m_infoName);
|
||||
}
|
||||
Status = adFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
// write to output file
|
||||
if (!Write(szBuffer, iLen))
|
||||
if (!Write(buffer, len))
|
||||
{
|
||||
Status = adFatalError;
|
||||
break;
|
||||
}
|
||||
iWrittenLen += iLen;
|
||||
writtenLen += len;
|
||||
|
||||
//detect end of file
|
||||
if (iWrittenLen == m_iContentLen || (m_iContentLen == -1 && m_bGZip && m_bConfirmedLength))
|
||||
if (writtenLen == m_contentLen || (m_contentLen == -1 && m_gzip && m_confirmedLength))
|
||||
{
|
||||
bEnd = true;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(szLineBuf);
|
||||
|
||||
#ifndef DISABLE_GZIP
|
||||
delete m_pGUnzipStream;
|
||||
m_gUnzipStream.reset();
|
||||
#endif
|
||||
|
||||
if (m_pOutFile)
|
||||
{
|
||||
fclose(m_pOutFile);
|
||||
}
|
||||
m_outFile.Close();
|
||||
|
||||
if (!bEnd && Status == adRunning && !IsStopped())
|
||||
if (!end && Status == adRunning && !IsStopped())
|
||||
{
|
||||
warn("URL %s failed: file incomplete", m_szInfoName);
|
||||
warn("URL %s failed: file incomplete", *m_infoName);
|
||||
Status = adFailed;
|
||||
}
|
||||
|
||||
if (bEnd)
|
||||
if (end)
|
||||
{
|
||||
Status = adFinished;
|
||||
}
|
||||
@@ -478,83 +412,85 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
|
||||
return Status;
|
||||
}
|
||||
|
||||
WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse)
|
||||
WebDownloader::EStatus WebDownloader::CheckResponse(const char* response)
|
||||
{
|
||||
if (!szResponse)
|
||||
if (!response)
|
||||
{
|
||||
if (!IsStopped())
|
||||
{
|
||||
warn("URL %s: Connection closed by remote host", m_szInfoName);
|
||||
warn("URL %s: Connection closed by remote host", *m_infoName);
|
||||
}
|
||||
return adConnectError;
|
||||
}
|
||||
|
||||
const char* szHTTPResponse = strchr(szResponse, ' ');
|
||||
if (strncmp(szResponse, "HTTP", 4) || !szHTTPResponse)
|
||||
const char* hTTPResponse = strchr(response, ' ');
|
||||
if (strncmp(response, "HTTP", 4) || !hTTPResponse)
|
||||
{
|
||||
warn("URL %s failed: %s", m_szInfoName, szResponse);
|
||||
warn("URL %s failed: %s", *m_infoName, response);
|
||||
return adFailed;
|
||||
}
|
||||
|
||||
szHTTPResponse++;
|
||||
hTTPResponse++;
|
||||
|
||||
if (!strncmp(szHTTPResponse, "400", 3) || !strncmp(szHTTPResponse, "499", 3))
|
||||
if (!strncmp(hTTPResponse, "400", 3) || !strncmp(hTTPResponse, "499", 3))
|
||||
{
|
||||
warn("URL %s failed: %s", m_szInfoName, szHTTPResponse);
|
||||
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
|
||||
return adConnectError;
|
||||
}
|
||||
else if (!strncmp(szHTTPResponse, "404", 3))
|
||||
else if (!strncmp(hTTPResponse, "404", 3))
|
||||
{
|
||||
warn("URL %s failed: %s", m_szInfoName, szHTTPResponse);
|
||||
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
|
||||
return adNotFound;
|
||||
}
|
||||
else if (!strncmp(szHTTPResponse, "301", 3) || !strncmp(szHTTPResponse, "302", 3))
|
||||
else if (!strncmp(hTTPResponse, "301", 3) || !strncmp(hTTPResponse, "302", 3) ||
|
||||
!strncmp(hTTPResponse, "303", 3) || !strncmp(hTTPResponse, "307", 3) ||
|
||||
!strncmp(hTTPResponse, "308", 3))
|
||||
{
|
||||
m_bRedirecting = true;
|
||||
m_redirecting = true;
|
||||
return adRunning;
|
||||
}
|
||||
else if (!strncmp(szHTTPResponse, "200", 3))
|
||||
else if (!strncmp(hTTPResponse, "200", 3))
|
||||
{
|
||||
// OK
|
||||
return adRunning;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
// unknown error, no special handling
|
||||
warn("URL %s failed: %s", m_szInfoName, szResponse);
|
||||
warn("URL %s failed: %s", *m_infoName, response);
|
||||
return adFailed;
|
||||
}
|
||||
}
|
||||
|
||||
void WebDownloader::ProcessHeader(const char* szLine)
|
||||
void WebDownloader::ProcessHeader(const char* line)
|
||||
{
|
||||
if (!strncasecmp(szLine, "Content-Length: ", 16))
|
||||
if (!strncasecmp(line, "Content-Length: ", 16))
|
||||
{
|
||||
m_iContentLen = atoi(szLine + 16);
|
||||
m_bConfirmedLength = true;
|
||||
m_contentLen = atoi(line + 16);
|
||||
m_confirmedLength = true;
|
||||
}
|
||||
else if (!strncasecmp(szLine, "Content-Encoding: gzip", 22))
|
||||
else if (!strncasecmp(line, "Content-Encoding: gzip", 22))
|
||||
{
|
||||
m_bGZip = true;
|
||||
m_gzip = true;
|
||||
}
|
||||
else if (!strncasecmp(szLine, "Content-Disposition: ", 21))
|
||||
else if (!strncasecmp(line, "Content-Disposition: ", 21))
|
||||
{
|
||||
ParseFilename(szLine);
|
||||
ParseFilename(line);
|
||||
}
|
||||
else if (m_bRedirecting && !strncasecmp(szLine, "Location: ", 10))
|
||||
else if (m_redirecting && !strncasecmp(line, "Location: ", 10))
|
||||
{
|
||||
ParseRedirect(szLine + 10);
|
||||
m_bRedirected = true;
|
||||
ParseRedirect(line + 10);
|
||||
m_redirected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WebDownloader::ParseFilename(const char* szContentDisposition)
|
||||
void WebDownloader::ParseFilename(const char* contentDisposition)
|
||||
{
|
||||
// Examples:
|
||||
// Content-Disposition: attachment; filename="fname.ext"
|
||||
// Content-Disposition: attachement;filename=fname.ext
|
||||
// Content-Disposition: attachement;filename=fname.ext;
|
||||
const char *p = strstr(szContentDisposition, "filename");
|
||||
const char *p = strstr(contentDisposition, "filename");
|
||||
if (!p)
|
||||
{
|
||||
return;
|
||||
@@ -570,9 +506,7 @@ void WebDownloader::ParseFilename(const char* szContentDisposition)
|
||||
|
||||
while (*p == ' ') p++;
|
||||
|
||||
char fname[1024];
|
||||
strncpy(fname, p, 1024);
|
||||
fname[1024-1] = '\0';
|
||||
BString<1024> fname = p;
|
||||
|
||||
char *pe = fname + strlen(fname) - 1;
|
||||
while ((*pe == ' ' || *pe == '\n' || *pe == '\r' || *pe == ';') && pe > fname) {
|
||||
@@ -582,67 +516,93 @@ void WebDownloader::ParseFilename(const char* szContentDisposition)
|
||||
|
||||
WebUtil::HttpUnquote(fname);
|
||||
|
||||
free(m_szOriginalFilename);
|
||||
m_szOriginalFilename = strdup(Util::BaseFileName(fname));
|
||||
m_originalFilename = FileSystem::BaseFileName(fname);
|
||||
|
||||
debug("OriginalFilename: %s", m_szOriginalFilename);
|
||||
debug("OriginalFilename: %s", *m_originalFilename);
|
||||
}
|
||||
|
||||
void WebDownloader::ParseRedirect(const char* szLocation)
|
||||
void WebDownloader::ParseRedirect(const char* location)
|
||||
{
|
||||
const char* szNewURL = szLocation;
|
||||
char szUrlBuf[1024];
|
||||
URL newUrl(szNewURL);
|
||||
const char* newLocation = location;
|
||||
BString<1024> urlBuf;
|
||||
URL newUrl(newLocation);
|
||||
if (!newUrl.IsValid())
|
||||
{
|
||||
// relative address
|
||||
URL oldUrl(m_szURL);
|
||||
if (oldUrl.GetPort() > 0)
|
||||
// redirect within host
|
||||
|
||||
BString<1024> resource;
|
||||
URL oldUrl(m_url);
|
||||
|
||||
if (*location == '/')
|
||||
{
|
||||
snprintf(szUrlBuf, 1024, "%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), szNewURL);
|
||||
// absolute path within host
|
||||
resource = location;
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(szUrlBuf, 1024, "%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), szNewURL);
|
||||
// relative path within host
|
||||
resource = oldUrl.GetResource();
|
||||
|
||||
char* p = strchr(resource, '?');
|
||||
if (p)
|
||||
{
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
p = strrchr(resource, '/');
|
||||
if (p)
|
||||
{
|
||||
p[1] = '\0';
|
||||
}
|
||||
|
||||
resource.Append(location);
|
||||
}
|
||||
szUrlBuf[1024-1] = '\0';
|
||||
szNewURL = szUrlBuf;
|
||||
|
||||
if (oldUrl.GetPort() > 0)
|
||||
{
|
||||
urlBuf.Format("%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), *resource);
|
||||
}
|
||||
else
|
||||
{
|
||||
urlBuf.Format("%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), *resource);
|
||||
}
|
||||
newLocation = urlBuf;
|
||||
}
|
||||
detail("URL %s redirected to %s", m_szURL, szNewURL);
|
||||
SetURL(szNewURL);
|
||||
detail("URL %s redirected to %s", *m_url, newLocation);
|
||||
SetUrl(newLocation);
|
||||
}
|
||||
|
||||
bool WebDownloader::Write(void* pBuffer, int iLen)
|
||||
bool WebDownloader::Write(void* buffer, int len)
|
||||
{
|
||||
if (!m_pOutFile && !PrepareFile())
|
||||
if (!m_outFile.Active() && !PrepareFile())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_GZIP
|
||||
if (m_bGZip)
|
||||
if (m_gzip)
|
||||
{
|
||||
m_pGUnzipStream->Write(pBuffer, iLen);
|
||||
const void *pOutBuf;
|
||||
int iOutLen = 1;
|
||||
while (iOutLen > 0)
|
||||
m_gUnzipStream->Write(buffer, len);
|
||||
const void *outBuf;
|
||||
int outLen = 1;
|
||||
while (outLen > 0)
|
||||
{
|
||||
GUnzipStream::EStatus eGZStatus = m_pGUnzipStream->Read(&pOutBuf, &iOutLen);
|
||||
GUnzipStream::EStatus gZStatus = m_gUnzipStream->Read(&outBuf, &outLen);
|
||||
|
||||
if (eGZStatus == GUnzipStream::zlError)
|
||||
if (gZStatus == GUnzipStream::zlError)
|
||||
{
|
||||
error("URL %s: GUnzip failed", m_szInfoName);
|
||||
error("URL %s: GUnzip failed", *m_infoName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iOutLen > 0 && fwrite(pOutBuf, 1, iOutLen, m_pOutFile) <= 0)
|
||||
if (outLen > 0 && m_outFile.Write(outBuf, outLen) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eGZStatus == GUnzipStream::zlFinished)
|
||||
if (gZStatus == GUnzipStream::zlFinished)
|
||||
{
|
||||
m_bConfirmedLength = true;
|
||||
m_confirmedLength = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -651,23 +611,22 @@ bool WebDownloader::Write(void* pBuffer, int iLen)
|
||||
else
|
||||
#endif
|
||||
|
||||
return fwrite(pBuffer, 1, iLen, m_pOutFile) > 0;
|
||||
return m_outFile.Write(buffer, len) > 0;
|
||||
}
|
||||
|
||||
bool WebDownloader::PrepareFile()
|
||||
{
|
||||
// prepare file for writing
|
||||
|
||||
const char* szFilename = m_szOutputFilename;
|
||||
m_pOutFile = fopen(szFilename, FOPEN_WB);
|
||||
if (!m_pOutFile)
|
||||
const char* filename = m_outputFilename;
|
||||
if (!m_outFile.Open(filename, DiskFile::omWrite))
|
||||
{
|
||||
error("Could not %s file %s", "create", szFilename);
|
||||
error("Could not %s file %s", "create", filename);
|
||||
return false;
|
||||
}
|
||||
if (g_pOptions->GetWriteBuffer() > 0)
|
||||
if (g_Options->GetWriteBuffer() > 0)
|
||||
{
|
||||
setvbuf(m_pOutFile, NULL, _IOFBF, g_pOptions->GetWriteBuffer() * 1024);
|
||||
m_outFile.SetWriteBuffer(g_Options->GetWriteBuffer() * 1024);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -675,57 +634,33 @@ bool WebDownloader::PrepareFile()
|
||||
|
||||
void WebDownloader::LogDebugInfo()
|
||||
{
|
||||
char szTime[50];
|
||||
#ifdef HAVE_CTIME_R_3
|
||||
ctime_r(&m_tLastUpdateTime, szTime, 50);
|
||||
#else
|
||||
ctime_r(&m_tLastUpdateTime, szTime);
|
||||
#endif
|
||||
|
||||
info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_eStatus, szTime, Util::BaseFileName(m_szOutputFilename));
|
||||
info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_status,
|
||||
*Util::FormatTime(m_lastUpdateTime), FileSystem::BaseFileName(m_outputFilename));
|
||||
}
|
||||
|
||||
void WebDownloader::Stop()
|
||||
{
|
||||
debug("Trying to stop WebDownloader");
|
||||
Thread::Stop();
|
||||
m_mutexConnection.Lock();
|
||||
if (m_pConnection)
|
||||
Guard guard(m_connectionMutex);
|
||||
if (m_connection)
|
||||
{
|
||||
m_pConnection->SetSuppressErrors(true);
|
||||
m_pConnection->Cancel();
|
||||
m_connection->SetSuppressErrors(true);
|
||||
m_connection->Cancel();
|
||||
}
|
||||
m_mutexConnection.Unlock();
|
||||
debug("WebDownloader stopped successfully");
|
||||
}
|
||||
|
||||
bool WebDownloader::Terminate()
|
||||
{
|
||||
Connection* pConnection = m_pConnection;
|
||||
bool terminated = Kill();
|
||||
if (terminated && pConnection)
|
||||
{
|
||||
debug("Terminating connection");
|
||||
pConnection->SetSuppressErrors(true);
|
||||
pConnection->Cancel();
|
||||
pConnection->Disconnect();
|
||||
delete pConnection;
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
|
||||
void WebDownloader::FreeConnection()
|
||||
{
|
||||
if (m_pConnection)
|
||||
if (m_connection)
|
||||
{
|
||||
debug("Releasing connection");
|
||||
m_mutexConnection.Lock();
|
||||
if (m_pConnection->GetStatus() == Connection::csCancelled)
|
||||
Guard guard(m_connectionMutex);
|
||||
if (m_connection->GetStatus() == Connection::csCancelled)
|
||||
{
|
||||
m_pConnection->Disconnect();
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
delete m_pConnection;
|
||||
m_pConnection = NULL;
|
||||
m_mutexConnection.Unlock();
|
||||
m_connection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,23 +14,18 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef WEBDOWNLOADER_H
|
||||
#define WEBDOWNLOADER_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Observer.h"
|
||||
#include "Thread.h"
|
||||
#include "Connection.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Util.h"
|
||||
|
||||
class WebDownloader : public Thread, public Subject
|
||||
@@ -48,65 +43,62 @@ public:
|
||||
adConnectError,
|
||||
adFatalError
|
||||
};
|
||||
|
||||
private:
|
||||
char* m_szURL;
|
||||
char* m_szOutputFilename;
|
||||
Connection* m_pConnection;
|
||||
Mutex m_mutexConnection;
|
||||
EStatus m_eStatus;
|
||||
time_t m_tLastUpdateTime;
|
||||
char* m_szInfoName;
|
||||
FILE* m_pOutFile;
|
||||
int m_iContentLen;
|
||||
bool m_bConfirmedLength;
|
||||
char* m_szOriginalFilename;
|
||||
bool m_bForce;
|
||||
bool m_bRedirecting;
|
||||
bool m_bRedirected;
|
||||
int m_iRedirects;
|
||||
bool m_bGZip;
|
||||
bool m_bRetry;
|
||||
#ifndef DISABLE_GZIP
|
||||
GUnzipStream* m_pGUnzipStream;
|
||||
#endif
|
||||
|
||||
void SetStatus(EStatus eStatus);
|
||||
bool Write(void* pBuffer, int iLen);
|
||||
bool PrepareFile();
|
||||
void FreeConnection();
|
||||
EStatus CheckResponse(const char* szResponse);
|
||||
EStatus CreateConnection(URL *pUrl);
|
||||
void ParseFilename(const char* szContentDisposition);
|
||||
void SendHeaders(URL *pUrl);
|
||||
EStatus DownloadHeaders();
|
||||
EStatus DownloadBody();
|
||||
void ParseRedirect(const char* szLocation);
|
||||
WebDownloader();
|
||||
EStatus GetStatus() { return m_status; }
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
EStatus Download();
|
||||
EStatus DownloadWithRedirects(int maxRedirects);
|
||||
void SetInfoName(const char* infoName) { m_infoName = infoName; }
|
||||
const char* GetInfoName() { return m_infoName; }
|
||||
void SetUrl(const char* url);
|
||||
const char* GetOutputFilename() { return m_outputFilename; }
|
||||
void SetOutputFilename(const char* outputFilename) { m_outputFilename = outputFilename; }
|
||||
time_t GetLastUpdateTime() { return m_lastUpdateTime; }
|
||||
void SetLastUpdateTimeNow();
|
||||
bool GetConfirmedLength() { return m_confirmedLength; }
|
||||
const char* GetOriginalFilename() { return m_originalFilename; }
|
||||
void SetForce(bool force) { m_force = force; }
|
||||
void SetRetry(bool retry) { m_retry = retry; }
|
||||
|
||||
void LogDebugInfo();
|
||||
|
||||
protected:
|
||||
virtual void ProcessHeader(const char* szLine);
|
||||
virtual void ProcessHeader(const char* line);
|
||||
|
||||
public:
|
||||
WebDownloader();
|
||||
virtual ~WebDownloader();
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
EStatus Download();
|
||||
bool Terminate();
|
||||
void SetInfoName(const char* v);
|
||||
const char* GetInfoName() { return m_szInfoName; }
|
||||
void SetURL(const char* szURL);
|
||||
const char* GetOutputFilename() { return m_szOutputFilename; }
|
||||
void SetOutputFilename(const char* v);
|
||||
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
|
||||
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
|
||||
bool GetConfirmedLength() { return m_bConfirmedLength; }
|
||||
const char* GetOriginalFilename() { return m_szOriginalFilename; }
|
||||
void SetForce(bool bForce) { m_bForce = bForce; }
|
||||
void SetRetry(bool bRetry) { m_bRetry = bRetry; }
|
||||
private:
|
||||
CString m_url;
|
||||
CString m_outputFilename;
|
||||
std::unique_ptr<Connection> m_connection;
|
||||
Mutex m_connectionMutex;
|
||||
EStatus m_status = adUndefined;
|
||||
time_t m_lastUpdateTime;
|
||||
CString m_infoName;
|
||||
DiskFile m_outFile;
|
||||
int m_contentLen;
|
||||
bool m_confirmedLength = false;
|
||||
CString m_originalFilename;
|
||||
bool m_force = false;
|
||||
bool m_redirecting;
|
||||
bool m_redirected;
|
||||
bool m_gzip;
|
||||
bool m_retry = true;
|
||||
#ifndef DISABLE_GZIP
|
||||
std::unique_ptr<GUnzipStream> m_gUnzipStream;
|
||||
#endif
|
||||
|
||||
void LogDebugInfo();
|
||||
void SetStatus(EStatus status);
|
||||
bool Write(void* buffer, int len);
|
||||
bool PrepareFile();
|
||||
void FreeConnection();
|
||||
EStatus CheckResponse(const char* response);
|
||||
EStatus CreateConnection(URL *url);
|
||||
void ParseFilename(const char* contentDisposition);
|
||||
void SendHeaders(URL *url);
|
||||
EStatus DownloadHeaders();
|
||||
EStatus DownloadBody();
|
||||
void ParseRedirect(const char* location);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
131
daemon/extension/CommandScript.cpp
Normal file
131
daemon/extension/CommandScript.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "CommandScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
static const int COMMANDPROCESS_SUCCESS = 93;
|
||||
static const int COMMANDPROCESS_ERROR = 94;
|
||||
|
||||
bool CommandScriptController::StartScript(const char* scriptName, const char* command,
|
||||
std::unique_ptr<Options::OptEntries> modifiedOptions)
|
||||
{
|
||||
CommandScriptController* scriptController = new CommandScriptController();
|
||||
scriptController->m_script = scriptName;
|
||||
scriptController->m_command = command;
|
||||
scriptController->m_logId = g_CommandScriptLog->Reset();
|
||||
scriptController->m_modifiedOptions = std::move(modifiedOptions);
|
||||
|
||||
scriptController->SetAutoDestroy(true);
|
||||
|
||||
scriptController->Start();
|
||||
|
||||
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandScriptController::Run()
|
||||
{
|
||||
ExecuteScriptList(m_script);
|
||||
}
|
||||
|
||||
void CommandScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Executing script %s with command %s", script->GetName(), *m_command);
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("script %s with command %s", script->GetName(), *m_command);
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
int exitCode = Execute();
|
||||
|
||||
infoName[0] = 'S'; // uppercase
|
||||
SetLogPrefix(nullptr);
|
||||
|
||||
switch (exitCode)
|
||||
{
|
||||
case COMMANDPROCESS_SUCCESS:
|
||||
PrintMessage(Message::mkInfo, "%s successful", *infoName);
|
||||
break;
|
||||
|
||||
case COMMANDPROCESS_ERROR:
|
||||
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
|
||||
PrintMessage(Message::mkError, "%s failed", *infoName);
|
||||
break;
|
||||
|
||||
default:
|
||||
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", *infoName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetEnvVar("NZBCP_COMMAND", m_command);
|
||||
|
||||
PrepareEnvScript(nullptr, scriptName);
|
||||
}
|
||||
|
||||
const char* CommandScriptController::GetOptValue(const char* name, const char* value)
|
||||
{
|
||||
Options::OptEntry* entry = m_modifiedOptions->FindOption(name);
|
||||
return entry ? entry->GetValue() : value;
|
||||
}
|
||||
|
||||
void CommandScriptController::AddMessage(Message::EKind kind, const char * text)
|
||||
{
|
||||
NzbScriptController::AddMessage(kind, text);
|
||||
g_CommandScriptLog->AddMessage(m_logId, kind, text);
|
||||
}
|
||||
|
||||
|
||||
int CommandScriptLog::Reset()
|
||||
{
|
||||
Guard guard(m_logMutex);
|
||||
m_messages.clear();
|
||||
return ++m_idScriptGen;
|
||||
}
|
||||
|
||||
void CommandScriptLog::AddMessage(int scriptId, Message::EKind kind, const char * text)
|
||||
{
|
||||
Guard guard(m_logMutex);
|
||||
|
||||
// save only messages from the last started script
|
||||
if (scriptId == m_idScriptGen)
|
||||
{
|
||||
m_messages.emplace_back(++m_idMessageGen, kind, Util::CurrentTime(), text);
|
||||
}
|
||||
}
|
||||
63
daemon/extension/CommandScript.h
Normal file
63
daemon/extension/CommandScript.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef COMMANDSCRIPT_H
|
||||
#define COMMANDSCRIPT_H
|
||||
|
||||
#include "NzbScript.h"
|
||||
#include "Log.h"
|
||||
|
||||
class CommandScriptController : public Thread, public NzbScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static bool StartScript(const char* scriptName, const char* command, std::unique_ptr<Options::OptEntries> modifiedOptions);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
virtual const char* GetOptValue(const char* name, const char* value);
|
||||
|
||||
private:
|
||||
CString m_script;
|
||||
CString m_command;
|
||||
int m_logId;
|
||||
std::unique_ptr<Options::OptEntries> m_modifiedOptions;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
};
|
||||
|
||||
class CommandScriptLog
|
||||
{
|
||||
public:
|
||||
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
|
||||
int Reset();
|
||||
void AddMessage(int scriptId, Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
MessageList m_messages;
|
||||
Mutex m_logMutex;
|
||||
int m_idMessageGen;
|
||||
int m_idScriptGen;
|
||||
};
|
||||
|
||||
extern CommandScriptLog* g_CommandScriptLog;
|
||||
|
||||
#endif
|
||||
80
daemon/extension/FeedScript.cpp
Normal file
80
daemon/extension/FeedScript.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2015-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "FeedScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
|
||||
static const int FEED_SUCCESS = 93;
|
||||
|
||||
void FeedScriptController::ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success)
|
||||
{
|
||||
FeedScriptController scriptController;
|
||||
scriptController.m_feedFile = feedFile;
|
||||
scriptController.m_feedId = feedId;
|
||||
|
||||
scriptController.ExecuteScriptList(feedScript);
|
||||
|
||||
if (success)
|
||||
{
|
||||
*success = scriptController.m_success;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
if (!script->GetFeedScript())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing feed-script %s for Feed%i", script->GetName(), m_feedId);
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("feed-script %s for Feed%i", script->GetName(), m_feedId);
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
int exitCode = Execute();
|
||||
|
||||
if (exitCode != FEED_SUCCESS)
|
||||
{
|
||||
infoName[0] = 'F'; // uppercase
|
||||
PrintMessage(Message::mkError, "%s failed", *infoName);
|
||||
m_success = false;
|
||||
}
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
}
|
||||
|
||||
void FeedScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetEnvVar("NZBFP_FILENAME", m_feedFile);
|
||||
SetIntEnvVar("NZBFP_FEEDID", m_feedId);
|
||||
|
||||
PrepareEnvScript(nullptr, scriptName);
|
||||
}
|
||||
42
daemon/extension/FeedScript.h
Normal file
42
daemon/extension/FeedScript.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2015-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FEEDSCRIPT_H
|
||||
#define FEEDSCRIPT_H
|
||||
|
||||
#include "NzbScript.h"
|
||||
|
||||
class FeedScriptController : public NzbScriptController
|
||||
{
|
||||
public:
|
||||
static void ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
|
||||
private:
|
||||
const char* m_feedFile;
|
||||
int m_feedId;
|
||||
bool m_success = true;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
};
|
||||
|
||||
#endif
|
||||
86
daemon/extension/NzbScript.cpp
Normal file
86
daemon/extension/NzbScript.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NzbScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
/**
|
||||
* If szStripPrefix is not nullptr, only pp-parameters, whose names start with the prefix
|
||||
* are processed. The prefix is then stripped from the names.
|
||||
* If szStripPrefix is nullptr, all pp-parameters are processed; without stripping.
|
||||
*/
|
||||
void NzbScriptController::PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix)
|
||||
{
|
||||
int prefixLen = stripPrefix ? strlen(stripPrefix) : 0;
|
||||
|
||||
for (NzbParameter& parameter : parameters)
|
||||
{
|
||||
const char* value = parameter.GetValue();
|
||||
|
||||
if (stripPrefix && !strncmp(parameter.GetName(), stripPrefix, prefixLen) && (int)strlen(parameter.GetName()) > prefixLen)
|
||||
{
|
||||
SetEnvVarSpecial("NZBPR", parameter.GetName() + prefixLen, value);
|
||||
}
|
||||
else if (!stripPrefix)
|
||||
{
|
||||
SetEnvVarSpecial("NZBPR", parameter.GetName(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NzbScriptController::PrepareEnvScript(NzbParameterList* parameters, const char* scriptName)
|
||||
{
|
||||
if (parameters)
|
||||
{
|
||||
PrepareEnvParameters(parameters, nullptr);
|
||||
}
|
||||
|
||||
BString<1024> paramPrefix("%s:", scriptName);
|
||||
|
||||
if (parameters)
|
||||
{
|
||||
PrepareEnvParameters(parameters, paramPrefix);
|
||||
}
|
||||
|
||||
PrepareEnvOptions(paramPrefix);
|
||||
}
|
||||
|
||||
void NzbScriptController::ExecuteScriptList(const char* scriptList)
|
||||
{
|
||||
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
|
||||
{
|
||||
if (scriptList && *scriptList)
|
||||
{
|
||||
// split szScriptList into tokens
|
||||
Tokenizer tok(scriptList, ",;");
|
||||
while (const char* scriptName = tok.Next())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
ExecuteScript(&script);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
daemon/extension/NzbScript.h
Normal file
37
daemon/extension/NzbScript.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NZBSCRIPT_H
|
||||
#define NZBSCRIPT_H
|
||||
|
||||
#include "Script.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "ScriptConfig.h"
|
||||
|
||||
class NzbScriptController : public ScriptController
|
||||
{
|
||||
protected:
|
||||
void PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix);
|
||||
void PrepareEnvScript(NzbParameterList* parameters, const char* scriptName);
|
||||
void ExecuteScriptList(const char* scriptList);
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
309
daemon/extension/PostScript.cpp
Normal file
309
daemon/extension/PostScript.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "PostScript.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
|
||||
static const int POSTPROCESS_PARCHECK = 92;
|
||||
static const int POSTPROCESS_SUCCESS = 93;
|
||||
static const int POSTPROCESS_ERROR = 94;
|
||||
static const int POSTPROCESS_NONE = 95;
|
||||
|
||||
void PostScriptController::StartJob(PostInfo* postInfo)
|
||||
{
|
||||
PostScriptController* scriptController = new PostScriptController();
|
||||
scriptController->m_postInfo = postInfo;
|
||||
scriptController->SetAutoDestroy(false);
|
||||
scriptController->m_prefixLen = 0;
|
||||
|
||||
postInfo->SetPostThread(scriptController);
|
||||
|
||||
scriptController->Start();
|
||||
}
|
||||
|
||||
void PostScriptController::Run()
|
||||
{
|
||||
StringBuilder scriptCommaList;
|
||||
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
for (NzbParameter& parameter : m_postInfo->GetNzbInfo()->GetParameters())
|
||||
{
|
||||
const char* varname = parameter.GetName();
|
||||
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname) - 1] == ':' &&
|
||||
(!strcasecmp(parameter.GetValue(), "yes") || !strcasecmp(parameter.GetValue(), "on") || !strcasecmp(parameter.GetValue(), "1")))
|
||||
{
|
||||
CString scriptName(varname);
|
||||
scriptName[strlen(scriptName) - 1] = '\0'; // remove trailing ':'
|
||||
scriptCommaList.Append(scriptName);
|
||||
scriptCommaList.Append(",");
|
||||
}
|
||||
}
|
||||
m_postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
|
||||
}
|
||||
|
||||
ExecuteScriptList(scriptCommaList);
|
||||
|
||||
m_postInfo->SetStage(PostInfo::ptFinished);
|
||||
m_postInfo->SetWorking(false);
|
||||
}
|
||||
|
||||
void PostScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
// if any script has requested par-check, do not execute other scripts
|
||||
if (!script->GetPostScript() || m_postInfo->GetRequestParCheck())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
|
||||
|
||||
BString<1024> progressLabel("Executing post-process-script %s", script->GetName());
|
||||
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->SetProgressLabel(progressLabel);
|
||||
}
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
|
||||
SetInfoName(infoName);
|
||||
|
||||
m_script = script;
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
int exitCode = Execute();
|
||||
|
||||
infoName[0] = 'P'; // uppercase
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
ScriptStatus::EStatus status = AnalyseExitCode(exitCode, infoName);
|
||||
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->GetNzbInfo()->GetScriptStatuses()->emplace_back(script->GetName(), status);
|
||||
}
|
||||
}
|
||||
|
||||
void PostScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
|
||||
ResetEnv();
|
||||
|
||||
SetIntEnvVar("NZBPP_NZBID", m_postInfo->GetNzbInfo()->GetId());
|
||||
SetEnvVar("NZBPP_NZBNAME", m_postInfo->GetNzbInfo()->GetName());
|
||||
SetEnvVar("NZBPP_DIRECTORY", m_postInfo->GetNzbInfo()->GetDestDir());
|
||||
SetEnvVar("NZBPP_NZBFILENAME", m_postInfo->GetNzbInfo()->GetFilename());
|
||||
SetEnvVar("NZBPP_QUEUEDFILE", m_postInfo->GetNzbInfo()->GetQueuedFilename());
|
||||
SetEnvVar("NZBPP_URL", m_postInfo->GetNzbInfo()->GetUrl());
|
||||
SetEnvVar("NZBPP_FINALDIR", m_postInfo->GetNzbInfo()->GetFinalDir());
|
||||
SetEnvVar("NZBPP_CATEGORY", m_postInfo->GetNzbInfo()->GetCategory());
|
||||
SetIntEnvVar("NZBPP_HEALTH", m_postInfo->GetNzbInfo()->CalcHealth());
|
||||
SetIntEnvVar("NZBPP_CRITICALHEALTH", m_postInfo->GetNzbInfo()->CalcCriticalHealth(false));
|
||||
|
||||
SetEnvVar("NZBPP_DUPEKEY", m_postInfo->GetNzbInfo()->GetDupeKey());
|
||||
SetIntEnvVar("NZBPP_DUPESCORE", m_postInfo->GetNzbInfo()->GetDupeScore());
|
||||
|
||||
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
|
||||
SetEnvVar("NZBPP_DUPEMODE", dupeModeName[m_postInfo->GetNzbInfo()->GetDupeMode()]);
|
||||
|
||||
BString<1024> status = m_postInfo->GetNzbInfo()->MakeTextStatus(true);
|
||||
SetEnvVar("NZBPP_STATUS", status);
|
||||
|
||||
char* detail = strchr(status, '/');
|
||||
if (detail) *detail = '\0';
|
||||
SetEnvVar("NZBPP_TOTALSTATUS", status);
|
||||
|
||||
const char* scriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" };
|
||||
SetEnvVar("NZBPP_SCRIPTSTATUS", scriptStatusName[m_postInfo->GetNzbInfo()->GetScriptStatuses()->CalcTotalStatus()]);
|
||||
|
||||
// deprecated
|
||||
int parStatusCodes[] = { 0, 0, 1, 2, 3, 4 };
|
||||
NzbInfo::EParStatus parStatus = m_postInfo->GetNzbInfo()->GetParStatus();
|
||||
// for downloads marked as bad and for deleted downloads pass par status "Failure"
|
||||
// for compatibility with older scripts which don't check "NZBPP_TOTALSTATUS"
|
||||
if (m_postInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsNone ||
|
||||
m_postInfo->GetNzbInfo()->GetMarkStatus() == NzbInfo::ksBad)
|
||||
{
|
||||
parStatus = NzbInfo::psFailure;
|
||||
}
|
||||
SetIntEnvVar("NZBPP_PARSTATUS", parStatusCodes[parStatus]);
|
||||
|
||||
// deprecated
|
||||
int unpackStatus[] = { 0, 0, 1, 2, 3, 4 };
|
||||
SetIntEnvVar("NZBPP_UNPACKSTATUS", unpackStatus[m_postInfo->GetNzbInfo()->GetUnpackStatus()]);
|
||||
|
||||
// deprecated
|
||||
SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsHealth);
|
||||
|
||||
SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_postInfo->GetNzbInfo()->GetTotalArticles());
|
||||
SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_postInfo->GetNzbInfo()->GetSuccessArticles());
|
||||
SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_postInfo->GetNzbInfo()->GetFailedArticles());
|
||||
|
||||
for (ServerStat& serverStat : m_postInfo->GetNzbInfo()->GetServerStats())
|
||||
{
|
||||
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_SUCCESSARTICLES", serverStat.GetServerId()),
|
||||
serverStat.GetSuccessArticles());
|
||||
|
||||
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_FAILEDARTICLES", serverStat.GetServerId()),
|
||||
serverStat.GetFailedArticles());
|
||||
}
|
||||
|
||||
PrepareEnvScript(m_postInfo->GetNzbInfo()->GetParameters(), scriptName);
|
||||
}
|
||||
|
||||
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode, const char* upInfoName)
|
||||
{
|
||||
// The ScriptStatus is accumulated for all scripts:
|
||||
// If any script has failed the status is "failure", etc.
|
||||
|
||||
switch (exitCode)
|
||||
{
|
||||
case POSTPROCESS_SUCCESS:
|
||||
PrintMessage(Message::mkInfo, "%s successful", upInfoName);
|
||||
return ScriptStatus::srSuccess;
|
||||
|
||||
case POSTPROCESS_ERROR:
|
||||
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
|
||||
PrintMessage(Message::mkError, "%s failed", upInfoName);
|
||||
return ScriptStatus::srFailure;
|
||||
|
||||
case POSTPROCESS_NONE:
|
||||
PrintMessage(Message::mkInfo, "%s skipped", upInfoName);
|
||||
return ScriptStatus::srNone;
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
case POSTPROCESS_PARCHECK:
|
||||
if (m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped)
|
||||
{
|
||||
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", upInfoName);
|
||||
return ScriptStatus::srFailure;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "%s requested par-check/repair", upInfoName);
|
||||
m_postInfo->SetRequestParCheck(true);
|
||||
m_postInfo->SetForceRepair(true);
|
||||
return ScriptStatus::srSuccess;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", upInfoName);
|
||||
return ScriptStatus::srFailure;
|
||||
}
|
||||
}
|
||||
|
||||
void PostScriptController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
const char* msgText = text + m_prefixLen;
|
||||
|
||||
if (!strncmp(msgText, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", msgText + 6);
|
||||
if (!strncmp(msgText + 6, "FINALDIR=", 9))
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->GetNzbInfo()->SetFinalDir(msgText + 6 + 9);
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "DIRECTORY=", 10))
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->GetNzbInfo()->SetDestDir(msgText + 6 + 10);
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "NZBPR_", 6))
|
||||
{
|
||||
CString param = msgText + 6 + 6;
|
||||
char* value = strchr(param, '=');
|
||||
if (value)
|
||||
{
|
||||
*value = '\0';
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->GetNzbInfo()->GetParameters()->SetParameter(param, value + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
||||
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
|
||||
{
|
||||
SetLogPrefix(nullptr);
|
||||
PrintMessage(Message::mkWarning, "Marking %s as bad", m_postInfo->GetNzbInfo()->GetName());
|
||||
SetLogPrefix(m_script->GetDisplayName());
|
||||
m_postInfo->GetNzbInfo()->SetMarkStatus(NzbInfo::ksBad);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
||||
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->SetProgressLabel(text);
|
||||
}
|
||||
|
||||
if (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority())
|
||||
{
|
||||
time_t stageTime = m_postInfo->GetStageTime();
|
||||
time_t startTime = m_postInfo->GetStartTime();
|
||||
time_t waitTime = Util::CurrentTime();
|
||||
|
||||
// wait until Post-processor is unpaused
|
||||
while (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
|
||||
{
|
||||
Util::Sleep(100);
|
||||
|
||||
// update time stamps
|
||||
|
||||
time_t delta = Util::CurrentTime() - waitTime;
|
||||
|
||||
if (stageTime > 0)
|
||||
{
|
||||
m_postInfo->SetStageTime(stageTime + delta);
|
||||
}
|
||||
|
||||
if (startTime > 0)
|
||||
{
|
||||
m_postInfo->SetStartTime(startTime + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PostScriptController::Stop()
|
||||
{
|
||||
debug("Stopping post-process-script");
|
||||
Thread::Stop();
|
||||
Terminate();
|
||||
}
|
||||
47
daemon/extension/PostScript.h
Normal file
47
daemon/extension/PostScript.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef POSTSCRIPT_H
|
||||
#define POSTSCRIPT_H
|
||||
|
||||
#include "Thread.h"
|
||||
#include "NzbScript.h"
|
||||
|
||||
class PostScriptController : public Thread, public NzbScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
static void StartJob(PostInfo* postInfo);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
PostInfo* m_postInfo;
|
||||
int m_prefixLen;
|
||||
ScriptConfig::Script* m_script;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
ScriptStatus::EStatus AnalyseExitCode(int exitCode, const char* upInfoName);
|
||||
};
|
||||
|
||||
#endif
|
||||
495
daemon/extension/QueueScript.cpp
Normal file
495
daemon/extension/QueueScript.cpp
Normal file
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NString.h"
|
||||
#include "QueueScript.h"
|
||||
#include "NzbScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
static const char* QUEUE_EVENT_NAMES[] = {
|
||||
"FILE_DOWNLOADED",
|
||||
"URL_COMPLETED",
|
||||
"NZB_MARKED",
|
||||
"NZB_ADDED",
|
||||
"NZB_NAMED",
|
||||
"NZB_DOWNLOADED",
|
||||
"NZB_DELETED" };
|
||||
|
||||
class QueueScriptController : public Thread, public NzbScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static void StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
CString m_nzbName;
|
||||
CString m_nzbFilename;
|
||||
CString m_url;
|
||||
CString m_category;
|
||||
CString m_destDir;
|
||||
CString m_queuedFilename;
|
||||
int m_id;
|
||||
int m_priority;
|
||||
CString m_dupeKey;
|
||||
EDupeMode m_dupeMode;
|
||||
int m_dupeScore;
|
||||
NzbParameterList m_parameters;
|
||||
int m_prefixLen;
|
||||
ScriptConfig::Script* m_script;
|
||||
QueueScriptCoordinator::EEvent m_event;
|
||||
bool m_markBad;
|
||||
NzbInfo::EDeleteStatus m_deleteStatus;
|
||||
NzbInfo::EUrlStatus m_urlStatus;
|
||||
NzbInfo::EMarkStatus m_markStatus;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
};
|
||||
|
||||
|
||||
void QueueScriptController::StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event)
|
||||
{
|
||||
QueueScriptController* scriptController = new QueueScriptController();
|
||||
|
||||
scriptController->m_nzbName = nzbInfo->GetName();
|
||||
scriptController->m_nzbFilename = nzbInfo->GetFilename();
|
||||
scriptController->m_url = nzbInfo->GetUrl();
|
||||
scriptController->m_category = nzbInfo->GetCategory();
|
||||
scriptController->m_destDir = nzbInfo->GetDestDir();
|
||||
scriptController->m_queuedFilename = nzbInfo->GetQueuedFilename();
|
||||
scriptController->m_id = nzbInfo->GetId();
|
||||
scriptController->m_priority = nzbInfo->GetPriority();
|
||||
scriptController->m_dupeKey = nzbInfo->GetDupeKey();
|
||||
scriptController->m_dupeMode = nzbInfo->GetDupeMode();
|
||||
scriptController->m_dupeScore = nzbInfo->GetDupeScore();
|
||||
scriptController->m_parameters.CopyFrom(nzbInfo->GetParameters());
|
||||
scriptController->m_script = script;
|
||||
scriptController->m_event = event;
|
||||
scriptController->m_prefixLen = 0;
|
||||
scriptController->m_markBad = false;
|
||||
scriptController->m_deleteStatus = nzbInfo->GetDeleteStatus();
|
||||
scriptController->m_urlStatus = nzbInfo->GetUrlStatus();
|
||||
scriptController->m_markStatus = nzbInfo->GetMarkStatus();
|
||||
scriptController->SetAutoDestroy(true);
|
||||
|
||||
scriptController->Start();
|
||||
}
|
||||
|
||||
void QueueScriptController::Run()
|
||||
{
|
||||
ExecuteScript(m_script);
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
|
||||
if (m_markBad)
|
||||
{
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_id);
|
||||
if (nzbInfo)
|
||||
{
|
||||
nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
|
||||
nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
|
||||
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
g_QueueScriptCoordinator->CheckQueue();
|
||||
}
|
||||
|
||||
void QueueScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
PrintMessage(m_event == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo,
|
||||
"Executing queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
Execute();
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
}
|
||||
|
||||
void QueueScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetEnvVar("NZBNA_NZBNAME", m_nzbName);
|
||||
SetIntEnvVar("NZBNA_NZBID", m_id);
|
||||
SetEnvVar("NZBNA_FILENAME", m_nzbFilename);
|
||||
SetEnvVar("NZBNA_DIRECTORY", m_destDir);
|
||||
SetEnvVar("NZBNA_QUEUEDFILE", m_queuedFilename);
|
||||
SetEnvVar("NZBNA_URL", m_url);
|
||||
SetEnvVar("NZBNA_CATEGORY", m_category);
|
||||
SetIntEnvVar("NZBNA_PRIORITY", m_priority);
|
||||
SetIntEnvVar("NZBNA_LASTID", m_id); // deprecated
|
||||
|
||||
SetEnvVar("NZBNA_DUPEKEY", m_dupeKey);
|
||||
SetIntEnvVar("NZBNA_DUPESCORE", m_dupeScore);
|
||||
|
||||
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
|
||||
SetEnvVar("NZBNA_DUPEMODE", dupeModeName[m_dupeMode]);
|
||||
|
||||
SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_event]);
|
||||
|
||||
const char* deleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" };
|
||||
SetEnvVar("NZBNA_DELETESTATUS", deleteStatusName[m_deleteStatus]);
|
||||
|
||||
const char* urlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" };
|
||||
SetEnvVar("NZBNA_URLSTATUS", urlStatusName[m_urlStatus]);
|
||||
|
||||
const char* markStatusName[] = { "NONE", "BAD", "GOOD", "SUCCESS" };
|
||||
SetEnvVar("NZBNA_MARKSTATUS", markStatusName[m_markStatus]);
|
||||
|
||||
PrepareEnvScript(&m_parameters, scriptName);
|
||||
}
|
||||
|
||||
void QueueScriptController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
const char* msgText = text + m_prefixLen;
|
||||
|
||||
if (!strncmp(msgText, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", msgText + 6);
|
||||
if (!strncmp(msgText + 6, "NZBPR_", 6))
|
||||
{
|
||||
CString param = msgText + 6 + 6;
|
||||
char* value = strchr(param, '=');
|
||||
if (value)
|
||||
{
|
||||
*value = '\0';
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
|
||||
if (nzbInfo)
|
||||
{
|
||||
nzbInfo->GetParameters()->SetParameter(param, value + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "DIRECTORY=", 10) &&
|
||||
m_event == QueueScriptCoordinator::qeNzbDownloaded)
|
||||
{
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
|
||||
if (nzbInfo)
|
||||
{
|
||||
nzbInfo->SetFinalDir(msgText + 6 + 10);
|
||||
}
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
|
||||
{
|
||||
m_markBad = true;
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
|
||||
if (nzbInfo)
|
||||
{
|
||||
nzbInfo->PrintMessage(Message::mkWarning, "Marking %s as bad", *m_nzbName);
|
||||
nzbInfo->SetMarkStatus(NzbInfo::ksBad);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NzbInfo* nzbInfo = nullptr;
|
||||
{
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
|
||||
if (nzbInfo)
|
||||
{
|
||||
nzbInfo->AddMessage(kind, text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nzbInfo)
|
||||
{
|
||||
ScriptController::AddMessage(kind, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QueueScriptCoordinator::InitOptions()
|
||||
{
|
||||
m_hasQueueScripts = false;
|
||||
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
|
||||
{
|
||||
if (script.GetQueueScript())
|
||||
{
|
||||
m_hasQueueScripts = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QueueScriptCoordinator::EnqueueScript(NzbInfo* nzbInfo, EEvent event)
|
||||
{
|
||||
if (!m_hasQueueScripts)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Guard guard(m_queueMutex);
|
||||
|
||||
if (event == qeNzbDownloaded)
|
||||
{
|
||||
// delete all other queued scripts for this nzb
|
||||
m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(),
|
||||
[nzbInfo](std::unique_ptr<QueueItem>& queueItem)
|
||||
{
|
||||
return queueItem->GetNzbId() == nzbInfo->GetId();
|
||||
}),
|
||||
m_queue.end());
|
||||
}
|
||||
|
||||
// respect option "EventInterval"
|
||||
time_t curTime = Util::CurrentTime();
|
||||
if (event == qeFileDownloaded &&
|
||||
(g_Options->GetEventInterval() == -1 ||
|
||||
(g_Options->GetEventInterval() > 0 && curTime - nzbInfo->GetQueueScriptTime() > 0 &&
|
||||
(int)(curTime - nzbInfo->GetQueueScriptTime()) < g_Options->GetEventInterval())))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
|
||||
{
|
||||
if (UsableScript(script, nzbInfo, event))
|
||||
{
|
||||
bool alreadyQueued = false;
|
||||
if (event == qeFileDownloaded)
|
||||
{
|
||||
// check if this script is already queued for this nzb
|
||||
for (QueueItem* queueItem : &m_queue)
|
||||
{
|
||||
if (queueItem->GetNzbId() == nzbInfo->GetId() && queueItem->GetScript() == &script)
|
||||
{
|
||||
alreadyQueued = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyQueued)
|
||||
{
|
||||
std::unique_ptr<QueueItem> queueItem = std::make_unique<QueueItem>(nzbInfo->GetId(), &script, event);
|
||||
if (m_curItem)
|
||||
{
|
||||
m_queue.push_back(std::move(queueItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_curItem = std::move(queueItem);
|
||||
QueueScriptController::StartScript(nzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
|
||||
}
|
||||
}
|
||||
|
||||
nzbInfo->SetQueueScriptTime(Util::CurrentTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event)
|
||||
{
|
||||
if (!script.GetQueueScript())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Util::EmptyStr(script.GetQueueEvents()) && !strstr(script.GetQueueEvents(), QUEUE_EVENT_NAMES[event]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check extension scripts assigned for that nzb
|
||||
for (NzbParameter& parameter : nzbInfo->GetParameters())
|
||||
{
|
||||
const char* varname = parameter.GetName();
|
||||
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname)-1] == ':' &&
|
||||
(!strcasecmp(parameter.GetValue(), "yes") ||
|
||||
!strcasecmp(parameter.GetValue(), "on") ||
|
||||
!strcasecmp(parameter.GetValue(), "1")))
|
||||
{
|
||||
BString<1024> scriptName = varname;
|
||||
scriptName[strlen(scriptName)-1] = '\0'; // remove trailing ':'
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for URL-events the extension scripts are not assigned yet;
|
||||
// instead we take the default extension scripts for the category (or global)
|
||||
if (event == qeUrlCompleted)
|
||||
{
|
||||
const char* postScript = g_Options->GetExtensions();
|
||||
if (!Util::EmptyStr(nzbInfo->GetCategory()))
|
||||
{
|
||||
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
|
||||
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
|
||||
{
|
||||
postScript = categoryObj->GetExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
if (!Util::EmptyStr(postScript))
|
||||
{
|
||||
Tokenizer tok(postScript, ",;");
|
||||
while (const char* scriptName = tok.Next())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NzbInfo* QueueScriptCoordinator::FindNzbInfo(DownloadQueue* downloadQueue, int nzbId)
|
||||
{
|
||||
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(nzbId);
|
||||
if (nzbInfo)
|
||||
{
|
||||
return nzbInfo;
|
||||
}
|
||||
|
||||
HistoryInfo* historyInfo = downloadQueue->GetHistory()->Find(nzbId);
|
||||
if (historyInfo)
|
||||
{
|
||||
return historyInfo->GetNzbInfo();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QueueScriptCoordinator::CheckQueue()
|
||||
{
|
||||
if (m_stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
Guard guard(m_queueMutex);
|
||||
|
||||
m_curItem.reset();
|
||||
NzbInfo* curNzbInfo = nullptr;
|
||||
Queue::iterator itCurItem;
|
||||
|
||||
for (Queue::iterator it = m_queue.begin(); it != m_queue.end(); )
|
||||
{
|
||||
std::unique_ptr<QueueItem>& queueItem = *it;
|
||||
|
||||
NzbInfo* nzbInfo = FindNzbInfo(downloadQueue, queueItem->GetNzbId());
|
||||
|
||||
// in a case this nzb must not be processed further - delete queue script from queue
|
||||
EEvent event = queueItem->GetEvent();
|
||||
bool ignoreEvent = !nzbInfo ||
|
||||
(nzbInfo->GetDeleteStatus() != NzbInfo::dsNone && event != qeNzbDeleted && event != qeNzbMarked) ||
|
||||
(nzbInfo->GetMarkStatus() == NzbInfo::ksBad && event != qeNzbMarked);
|
||||
|
||||
if (ignoreEvent)
|
||||
{
|
||||
it = m_queue.erase(it);
|
||||
if (curNzbInfo)
|
||||
{
|
||||
// process from the beginning, while "erase" invalidated "itCurItem"
|
||||
curNzbInfo = nullptr;
|
||||
it = m_queue.begin();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_curItem || queueItem->GetEvent() > m_curItem->GetEvent())
|
||||
{
|
||||
itCurItem = it;
|
||||
curNzbInfo = nzbInfo;
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
if (curNzbInfo)
|
||||
{
|
||||
m_curItem = std::move(*itCurItem);
|
||||
m_queue.erase(itCurItem);
|
||||
QueueScriptController::StartScript(curNzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
|
||||
}
|
||||
}
|
||||
|
||||
bool QueueScriptCoordinator::HasJob(int nzbId, bool* active)
|
||||
{
|
||||
Guard guard(m_queueMutex);
|
||||
|
||||
bool working = m_curItem && m_curItem->GetNzbId() == nzbId;
|
||||
if (active)
|
||||
{
|
||||
*active = working;
|
||||
}
|
||||
if (!working)
|
||||
{
|
||||
for (QueueItem* queueItem : &m_queue)
|
||||
{
|
||||
working = queueItem->GetNzbId() == nzbId;
|
||||
if (working)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return working;
|
||||
}
|
||||
|
||||
int QueueScriptCoordinator::GetQueueSize()
|
||||
{
|
||||
Guard guard(m_queueMutex);
|
||||
|
||||
int queuedCount = m_queue.size();
|
||||
if (m_curItem)
|
||||
{
|
||||
queuedCount++;
|
||||
}
|
||||
|
||||
return queuedCount;
|
||||
}
|
||||
77
daemon/extension/QueueScript.h
Normal file
77
daemon/extension/QueueScript.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef QUEUESCRIPT_H
|
||||
#define QUEUESCRIPT_H
|
||||
|
||||
#include "DownloadInfo.h"
|
||||
#include "ScriptConfig.h"
|
||||
|
||||
class QueueScriptCoordinator
|
||||
{
|
||||
public:
|
||||
enum EEvent
|
||||
{
|
||||
qeFileDownloaded, // lowest priority
|
||||
qeUrlCompleted,
|
||||
qeNzbMarked,
|
||||
qeNzbAdded,
|
||||
qeNzbNamed,
|
||||
qeNzbDownloaded,
|
||||
qeNzbDeleted // highest priority
|
||||
};
|
||||
|
||||
void Stop() { m_stopped = true; }
|
||||
void InitOptions();
|
||||
void EnqueueScript(NzbInfo* nzbInfo, EEvent event);
|
||||
void CheckQueue();
|
||||
bool HasJob(int nzbId, bool* active);
|
||||
int GetQueueSize();
|
||||
static NzbInfo* FindNzbInfo(DownloadQueue* downloadQueue, int nzbId);
|
||||
|
||||
private:
|
||||
class QueueItem
|
||||
{
|
||||
public:
|
||||
QueueItem(int nzbId, ScriptConfig::Script* script, EEvent event) :
|
||||
m_nzbId(nzbId), m_script(script), m_event(event) {}
|
||||
int GetNzbId() { return m_nzbId; }
|
||||
ScriptConfig::Script* GetScript() { return m_script; }
|
||||
EEvent GetEvent() { return m_event; }
|
||||
private:
|
||||
int m_nzbId;
|
||||
ScriptConfig::Script* m_script;
|
||||
EEvent m_event;
|
||||
};
|
||||
|
||||
typedef std::deque<std::unique_ptr<QueueItem>> Queue;
|
||||
|
||||
Queue m_queue;
|
||||
Mutex m_queueMutex;
|
||||
std::unique_ptr<QueueItem> m_curItem;
|
||||
bool m_hasQueueScripts = false;
|
||||
bool m_stopped = false;
|
||||
|
||||
bool UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event);
|
||||
};
|
||||
|
||||
extern QueueScriptCoordinator* g_QueueScriptCoordinator;
|
||||
|
||||
#endif
|
||||
200
daemon/extension/ScanScript.cpp
Normal file
200
daemon/extension/ScanScript.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "ScanScript.h"
|
||||
#include "Scanner.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
class ScanScriptCheck : public NzbScriptController
|
||||
{
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script) { has |= script->GetScanScript(); }
|
||||
bool has = false;
|
||||
friend class ScanScriptController;
|
||||
};
|
||||
|
||||
|
||||
bool ScanScriptController::HasScripts()
|
||||
{
|
||||
ScanScriptCheck check;
|
||||
check.ExecuteScriptList(g_Options->GetExtensions());
|
||||
return check.has;
|
||||
}
|
||||
|
||||
void ScanScriptController::ExecuteScripts(const char* nzbFilename,
|
||||
const char* url, const char* directory, CString* nzbName, CString* category,
|
||||
int* priority, NzbParameterList* parameters, bool* addTop, bool* addPaused,
|
||||
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode)
|
||||
{
|
||||
ScanScriptController scriptController;
|
||||
scriptController.m_nzbFilename = nzbFilename;
|
||||
scriptController.m_url = url;
|
||||
scriptController.m_directory = directory;
|
||||
scriptController.m_nzbName = nzbName;
|
||||
scriptController.m_category = category;
|
||||
scriptController.m_parameters = parameters;
|
||||
scriptController.m_priority = priority;
|
||||
scriptController.m_addTop = addTop;
|
||||
scriptController.m_addPaused = addPaused;
|
||||
scriptController.m_dupeKey = dupeKey;
|
||||
scriptController.m_dupeScore = dupeScore;
|
||||
scriptController.m_dupeMode = dupeMode;
|
||||
scriptController.m_prefixLen = 0;
|
||||
|
||||
const char* extensions = g_Options->GetExtensions();
|
||||
|
||||
if (!Util::EmptyStr(*category))
|
||||
{
|
||||
Options::Category* categoryObj = g_Options->FindCategory(*category, false);
|
||||
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
|
||||
{
|
||||
extensions = categoryObj->GetExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
scriptController.ExecuteScriptList(extensions);
|
||||
}
|
||||
|
||||
void ScanScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
if (!script->GetScanScript() || !FileSystem::FileExists(m_nzbFilename))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
Execute();
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
}
|
||||
|
||||
void ScanScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetEnvVar("NZBNP_FILENAME", m_nzbFilename);
|
||||
SetEnvVar("NZBNP_URL", m_url);
|
||||
SetEnvVar("NZBNP_NZBNAME", strlen(*m_nzbName) > 0 ? **m_nzbName : FileSystem::BaseFileName(m_nzbFilename));
|
||||
SetEnvVar("NZBNP_CATEGORY", *m_category);
|
||||
SetIntEnvVar("NZBNP_PRIORITY", *m_priority);
|
||||
SetIntEnvVar("NZBNP_TOP", *m_addTop ? 1 : 0);
|
||||
SetIntEnvVar("NZBNP_PAUSED", *m_addPaused ? 1 : 0);
|
||||
SetEnvVar("NZBNP_DUPEKEY", *m_dupeKey);
|
||||
SetIntEnvVar("NZBNP_DUPESCORE", *m_dupeScore);
|
||||
|
||||
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
|
||||
SetEnvVar("NZBNP_DUPEMODE", dupeModeName[*m_dupeMode]);
|
||||
|
||||
// remove trailing slash
|
||||
BString<1024> dir = m_directory;
|
||||
int len = strlen(dir);
|
||||
if (dir[len-1] == PATH_SEPARATOR)
|
||||
{
|
||||
dir[len-1] = '\0';
|
||||
}
|
||||
SetEnvVar("NZBNP_DIRECTORY", dir);
|
||||
|
||||
PrepareEnvScript(m_parameters, scriptName);
|
||||
}
|
||||
|
||||
void ScanScriptController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
const char* msgText = text + m_prefixLen;
|
||||
|
||||
if (!strncmp(msgText, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", msgText + 6);
|
||||
if (!strncmp(msgText + 6, "NZBNAME=", 8))
|
||||
{
|
||||
*m_nzbName = msgText + 6 + 8;
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "CATEGORY=", 9))
|
||||
{
|
||||
*m_category = msgText + 6 + 9;
|
||||
g_Scanner->InitPPParameters(*m_category, m_parameters, true);
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "NZBPR_", 6))
|
||||
{
|
||||
CString param = msgText + 6 + 6;
|
||||
char* value = strchr(param, '=');
|
||||
if (value)
|
||||
{
|
||||
*value = '\0';
|
||||
m_parameters->SetParameter(param, value + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "PRIORITY=", 9))
|
||||
{
|
||||
*m_priority = atoi(msgText + 6 + 9);
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "TOP=", 4))
|
||||
{
|
||||
*m_addTop = atoi(msgText + 6 + 4) != 0;
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "PAUSED=", 7))
|
||||
{
|
||||
*m_addPaused = atoi(msgText + 6 + 7) != 0;
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "DUPEKEY=", 8))
|
||||
{
|
||||
*m_dupeKey = msgText + 6 + 8;
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "DUPESCORE=", 10))
|
||||
{
|
||||
*m_dupeScore = atoi(msgText + 6 + 10);
|
||||
}
|
||||
else if (!strncmp(msgText + 6, "DUPEMODE=", 9))
|
||||
{
|
||||
const char* dupeMode = msgText + 6 + 9;
|
||||
if (strcasecmp(dupeMode, "score") && strcasecmp(dupeMode, "all") && strcasecmp(dupeMode, "force"))
|
||||
{
|
||||
error("Invalid value \"%s\" for command \"DUPEMODE\" received from %s", dupeMode, GetInfoName());
|
||||
return;
|
||||
}
|
||||
*m_dupeMode = !strcasecmp(dupeMode, "all") ? dmAll :
|
||||
!strcasecmp(dupeMode, "force") ? dmForce : dmScore;
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptController::AddMessage(kind, text);
|
||||
}
|
||||
}
|
||||
57
daemon/extension/ScanScript.h
Normal file
57
daemon/extension/ScanScript.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SCANSCRIPT_H
|
||||
#define SCANSCRIPT_H
|
||||
|
||||
#include "NzbScript.h"
|
||||
|
||||
class ScanScriptController : public NzbScriptController
|
||||
{
|
||||
public:
|
||||
static void ExecuteScripts(const char* nzbFilename, const char* url,
|
||||
const char* directory, CString* nzbName, CString* category, int* priority,
|
||||
NzbParameterList* parameters, bool* addTop, bool* addPaused,
|
||||
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode);
|
||||
static bool HasScripts();
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
const char* m_nzbFilename;
|
||||
const char* m_url;
|
||||
const char* m_directory;
|
||||
CString* m_nzbName;
|
||||
CString* m_category;
|
||||
int* m_priority;
|
||||
NzbParameterList* m_parameters;
|
||||
bool* m_addTop;
|
||||
bool* m_addPaused;
|
||||
CString* m_dupeKey;
|
||||
int* m_dupeScore;
|
||||
EDupeMode* m_dupeMode;
|
||||
int m_prefixLen;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
};
|
||||
|
||||
#endif
|
||||
114
daemon/extension/SchedulerScript.cpp
Normal file
114
daemon/extension/SchedulerScript.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "SchedulerScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
void SchedulerScriptController::StartScript(const char* param, bool externalProcess, int taskId)
|
||||
{
|
||||
std::vector<CString> argv;
|
||||
if (externalProcess && (argv = Util::SplitCommandLine(param)).empty())
|
||||
{
|
||||
error("Could not execute scheduled process-script, failed to parse command line: %s", param);
|
||||
return;
|
||||
}
|
||||
|
||||
SchedulerScriptController* scriptController = new SchedulerScriptController();
|
||||
|
||||
scriptController->m_externalProcess = externalProcess;
|
||||
scriptController->m_script = param;
|
||||
scriptController->m_taskId = taskId;
|
||||
|
||||
if (externalProcess)
|
||||
{
|
||||
scriptController->SetArgs(std::move(argv));
|
||||
}
|
||||
|
||||
scriptController->SetAutoDestroy(true);
|
||||
|
||||
scriptController->Start();
|
||||
}
|
||||
|
||||
void SchedulerScriptController::Run()
|
||||
{
|
||||
if (m_externalProcess)
|
||||
{
|
||||
ExecuteExternalProcess();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteScriptList(m_script);
|
||||
}
|
||||
}
|
||||
|
||||
void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
if (!script->GetSchedulerScript())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BString<1024> taskName(" for Task%i", m_taskId);
|
||||
if (m_taskId == 0)
|
||||
{
|
||||
taskName = "";
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing scheduler-script %s%s", script->GetName(), *taskName);
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("scheduler-script %s%s", script->GetName(), *taskName);
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
Execute();
|
||||
|
||||
SetLogPrefix(nullptr);
|
||||
}
|
||||
|
||||
void SchedulerScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetIntEnvVar("NZBSP_TASKID", m_taskId);
|
||||
|
||||
PrepareEnvScript(nullptr, scriptName);
|
||||
}
|
||||
|
||||
void SchedulerScriptController::ExecuteExternalProcess()
|
||||
{
|
||||
info("Executing scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
|
||||
|
||||
BString<1024> infoName("scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
|
||||
SetInfoName(infoName);
|
||||
|
||||
BString<1024> logPrefix = FileSystem::BaseFileName(GetScript());
|
||||
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
SetLogPrefix(logPrefix);
|
||||
|
||||
Execute();
|
||||
}
|
||||
45
daemon/extension/SchedulerScript.h
Normal file
45
daemon/extension/SchedulerScript.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SCHEDULERSCRIPT_H
|
||||
#define SCHEDULERSCRIPT_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "NzbScript.h"
|
||||
|
||||
class SchedulerScriptController : public Thread, public NzbScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static void StartScript(const char* param, bool externalProcess, int taskId);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
|
||||
private:
|
||||
CString m_script;
|
||||
bool m_externalProcess;
|
||||
int m_taskId;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
void ExecuteExternalProcess();
|
||||
};
|
||||
|
||||
#endif
|
||||
500
daemon/extension/ScriptConfig.cpp
Normal file
500
daemon/extension/ScriptConfig.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "ScriptConfig.h"
|
||||
|
||||
static const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET ";
|
||||
static const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING";
|
||||
static const char* SCAN_SCRIPT_SIGNATURE = "SCAN";
|
||||
static const char* QUEUE_SCRIPT_SIGNATURE = "QUEUE";
|
||||
static const char* SCHEDULER_SCRIPT_SIGNATURE = "SCHEDULER";
|
||||
static const char* FEED_SCRIPT_SIGNATURE = "FEED";
|
||||
static const char* END_SCRIPT_SIGNATURE = " SCRIPT";
|
||||
static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:";
|
||||
static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
|
||||
static const char* DEFINITION_SIGNATURE = "###";
|
||||
|
||||
void ScriptConfig::InitOptions()
|
||||
{
|
||||
InitScripts();
|
||||
InitConfigTemplates();
|
||||
CreateTasks();
|
||||
}
|
||||
|
||||
bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
|
||||
{
|
||||
// read config file
|
||||
DiskFile infile;
|
||||
|
||||
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename());
|
||||
CString buf;
|
||||
buf.Reserve(fileLen);
|
||||
|
||||
while (infile.ReadLine(buf, fileLen + 1))
|
||||
{
|
||||
// remove trailing '\n' and '\r' and spaces
|
||||
Util::TrimRight(buf);
|
||||
|
||||
// skip comments and empty lines
|
||||
if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CString optname;
|
||||
CString optvalue;
|
||||
if (Options::SplitOptionString(buf, optname, optvalue))
|
||||
{
|
||||
optEntries->emplace_back(optname, optvalue);
|
||||
}
|
||||
}
|
||||
|
||||
infile.Close();
|
||||
|
||||
Options::ConvertOldOptions(optEntries);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptConfig::SaveConfig(Options::OptEntries* optEntries)
|
||||
{
|
||||
// save to config file
|
||||
DiskFile infile;
|
||||
|
||||
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omReadWrite))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<CString> config;
|
||||
std::set<Options::OptEntry*> writtenOptions;
|
||||
|
||||
// read config file into memory array
|
||||
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename()) + 1;
|
||||
CString content;
|
||||
content.Reserve(fileLen);
|
||||
while (infile.ReadLine(content, fileLen + 1))
|
||||
{
|
||||
config.push_back(*content);
|
||||
}
|
||||
content.Clear();
|
||||
|
||||
// write config file back to disk, replace old values of existing options with new values
|
||||
infile.Seek(0);
|
||||
for (CString& buf : config)
|
||||
{
|
||||
const char* eq = strchr(buf, '=');
|
||||
if (eq && buf[0] != '#')
|
||||
{
|
||||
// remove trailing '\n' and '\r' and spaces
|
||||
buf.TrimRight();
|
||||
|
||||
CString optname;
|
||||
CString optvalue;
|
||||
if (g_Options->SplitOptionString(buf, optname, optvalue))
|
||||
{
|
||||
Options::OptEntry* optEntry = optEntries->FindOption(optname);
|
||||
if (optEntry)
|
||||
{
|
||||
infile.Print("%s=%s\n", optEntry->GetName(), optEntry->GetValue());
|
||||
writtenOptions.insert(optEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infile.Print("%s", *buf);
|
||||
}
|
||||
}
|
||||
|
||||
// write new options
|
||||
for (Options::OptEntry& optEntry : *optEntries)
|
||||
{
|
||||
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(&optEntry);
|
||||
if (fit == writtenOptions.end())
|
||||
{
|
||||
infile.Print("%s=%s\n", optEntry.GetName(), optEntry.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
// close and truncate the file
|
||||
int pos = (int)infile.Position();
|
||||
infile.Close();
|
||||
|
||||
FileSystem::TruncateFile(g_Options->GetConfigFilename(), pos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
|
||||
{
|
||||
CharBuffer buffer;
|
||||
if (!FileSystem::LoadFileIntoBuffer(g_Options->GetConfigTemplate(), buffer, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
configTemplates->emplace_back(Script("", ""), buffer);
|
||||
|
||||
if (!g_Options->GetScriptDir())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Scripts scriptList;
|
||||
LoadScripts(&scriptList);
|
||||
|
||||
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
||||
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
|
||||
|
||||
for (Script& script : scriptList)
|
||||
{
|
||||
DiskFile infile;
|
||||
if (!infile.Open(script.GetLocation(), DiskFile::omRead))
|
||||
{
|
||||
configTemplates->emplace_back(std::move(script), "");
|
||||
continue;
|
||||
}
|
||||
|
||||
StringBuilder templ;
|
||||
char buf[1024];
|
||||
bool inConfig = false;
|
||||
bool inHeader = false;
|
||||
|
||||
while (infile.ReadLine(buf, sizeof(buf) - 1))
|
||||
{
|
||||
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
|
||||
strstr(buf, END_SCRIPT_SIGNATURE) &&
|
||||
(strstr(buf, POST_SCRIPT_SIGNATURE) ||
|
||||
strstr(buf, SCAN_SCRIPT_SIGNATURE) ||
|
||||
strstr(buf, QUEUE_SCRIPT_SIGNATURE) ||
|
||||
strstr(buf, SCHEDULER_SCRIPT_SIGNATURE) ||
|
||||
strstr(buf, FEED_SCRIPT_SIGNATURE)))
|
||||
{
|
||||
if (inConfig)
|
||||
{
|
||||
break;
|
||||
}
|
||||
inConfig = true;
|
||||
inHeader = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
|
||||
|
||||
if (inConfig && !inHeader)
|
||||
{
|
||||
templ.Append(buf);
|
||||
}
|
||||
}
|
||||
|
||||
infile.Close();
|
||||
|
||||
configTemplates->emplace_back(std::move(script), templ);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptConfig::InitConfigTemplates()
|
||||
{
|
||||
if (!LoadConfigTemplates(&m_configTemplates))
|
||||
{
|
||||
error("Could not read configuration templates");
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptConfig::InitScripts()
|
||||
{
|
||||
LoadScripts(&m_scripts);
|
||||
}
|
||||
|
||||
void ScriptConfig::LoadScripts(Scripts* scripts)
|
||||
{
|
||||
if (Util::EmptyStr(g_Options->GetScriptDir()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scripts tmpScripts;
|
||||
|
||||
Tokenizer tokDir(g_Options->GetScriptDir(), ",;");
|
||||
while (const char* scriptDir = tokDir.Next())
|
||||
{
|
||||
LoadScriptDir(&tmpScripts, scriptDir, false);
|
||||
}
|
||||
|
||||
tmpScripts.sort(
|
||||
[](Script& script1, Script& script2)
|
||||
{
|
||||
return strcmp(script1.GetName(), script2.GetName()) < 0;
|
||||
});
|
||||
|
||||
// first add all scripts from ScriptOrder
|
||||
Tokenizer tokOrder(g_Options->GetScriptOrder(), ",;");
|
||||
while (const char* scriptName = tokOrder.Next())
|
||||
{
|
||||
Scripts::iterator pos = std::find_if(tmpScripts.begin(), tmpScripts.end(),
|
||||
[scriptName](Script& script)
|
||||
{
|
||||
return !strcmp(script.GetName(), scriptName);
|
||||
});
|
||||
|
||||
if (pos != tmpScripts.end())
|
||||
{
|
||||
scripts->splice(scripts->end(), tmpScripts, pos);
|
||||
}
|
||||
}
|
||||
|
||||
// then add all other scripts from scripts directory
|
||||
scripts->splice(scripts->end(), std::move(tmpScripts));
|
||||
|
||||
BuildScriptDisplayNames(scripts);
|
||||
}
|
||||
|
||||
void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir)
|
||||
{
|
||||
DirBrowser dir(directory);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
if (filename[0] != '.' && filename[0] != '_')
|
||||
{
|
||||
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
|
||||
|
||||
if (!FileSystem::DirectoryExists(fullFilename))
|
||||
{
|
||||
BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
|
||||
if (ScriptExists(scripts, scriptName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Script script(scriptName, fullFilename);
|
||||
if (LoadScriptFile(&script))
|
||||
{
|
||||
scripts->push_back(std::move(script));
|
||||
}
|
||||
}
|
||||
else if (!isSubDir)
|
||||
{
|
||||
LoadScriptDir(scripts, fullFilename, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptConfig::LoadScriptFile(Script* script)
|
||||
{
|
||||
DiskFile infile;
|
||||
if (!infile.Open(script->GetLocation(), DiskFile::omRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CharBuffer buffer(1024 * 10 + 1);
|
||||
|
||||
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
||||
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
|
||||
const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
|
||||
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
|
||||
|
||||
// check if the file contains pp-script-signature
|
||||
// read first 10KB of the file and look for signature
|
||||
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
|
||||
infile.Close();
|
||||
buffer[readBytes] = '\0';
|
||||
|
||||
bool postScript = false;
|
||||
bool scanScript = false;
|
||||
bool queueScript = false;
|
||||
bool schedulerScript = false;
|
||||
bool feedScript = false;
|
||||
char* queueEvents = nullptr;
|
||||
char* taskTime = nullptr;
|
||||
|
||||
bool inConfig = false;
|
||||
bool afterConfig = false;
|
||||
|
||||
// Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
|
||||
// - in script definition body (between opening and closing script signatures);
|
||||
// - immediately before script definition (before opening script signature);
|
||||
// - immediately after script definition (after closing script signature).
|
||||
// The last two pissibilities are provided to increase compatibility of scripts with older
|
||||
// nzbget versions which do not expect the extra declarations in the script defintion body.
|
||||
|
||||
Tokenizer tok(buffer, "\n\r", true);
|
||||
while (char* line = tok.Next())
|
||||
{
|
||||
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
|
||||
{
|
||||
queueEvents = line + queueEventsSignatureLen;
|
||||
}
|
||||
else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
|
||||
{
|
||||
taskTime = line + taskTimeSignatureLen;
|
||||
}
|
||||
|
||||
bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
|
||||
if (!header && !inConfig)
|
||||
{
|
||||
queueEvents = nullptr;
|
||||
taskTime = nullptr;
|
||||
}
|
||||
|
||||
if (!header && afterConfig)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
|
||||
{
|
||||
if (!inConfig)
|
||||
{
|
||||
inConfig = true;
|
||||
postScript = strstr(line, POST_SCRIPT_SIGNATURE);
|
||||
scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
|
||||
queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
|
||||
schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
|
||||
feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
|
||||
}
|
||||
else
|
||||
{
|
||||
afterConfig = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// trim decorations
|
||||
char* p;
|
||||
while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
|
||||
if (queueEvents) queueEvents = Util::Trim(queueEvents);
|
||||
while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
|
||||
if (taskTime) taskTime = Util::Trim(taskTime);
|
||||
|
||||
script->SetPostScript(postScript);
|
||||
script->SetScanScript(scanScript);
|
||||
script->SetQueueScript(queueScript);
|
||||
script->SetSchedulerScript(schedulerScript);
|
||||
script->SetFeedScript(feedScript);
|
||||
script->SetQueueEvents(queueEvents);
|
||||
script->SetTaskTime(taskTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
|
||||
{
|
||||
if (isSubDir)
|
||||
{
|
||||
BString<1024> directory2 = directory;
|
||||
int len = strlen(directory2);
|
||||
if (directory2[len-1] == PATH_SEPARATOR || directory2[len-1] == ALT_PATH_SEPARATOR)
|
||||
{
|
||||
// trim last path-separator
|
||||
directory2[len-1] = '\0';
|
||||
}
|
||||
|
||||
return BString<1024>("%s%c%s", FileSystem::BaseFileName(directory2), PATH_SEPARATOR, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptConfig::ScriptExists(Scripts* scripts, const char* scriptName)
|
||||
{
|
||||
return std::find_if(scripts->begin(), scripts->end(),
|
||||
[scriptName](Script& script)
|
||||
{
|
||||
return !strcmp(script.GetName(), scriptName);
|
||||
}) != scripts->end();
|
||||
}
|
||||
|
||||
void ScriptConfig::BuildScriptDisplayNames(Scripts* scripts)
|
||||
{
|
||||
// trying to use short name without path and extension.
|
||||
// if there are other scripts with the same short name - using a longer name instead (with ot without extension)
|
||||
|
||||
for (Script& script : scripts)
|
||||
{
|
||||
BString<1024> shortName = script.GetName();
|
||||
if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension
|
||||
|
||||
const char* displayName = FileSystem::BaseFileName(shortName);
|
||||
|
||||
for (Script& script2 : scripts)
|
||||
{
|
||||
BString<1024> shortName2 = script2.GetName();
|
||||
if (char* ext = strrchr(shortName2, '.')) *ext = '\0'; // strip file extension
|
||||
|
||||
const char* displayName2 = FileSystem::BaseFileName(shortName2);
|
||||
|
||||
if (!strcmp(displayName, displayName2) && script.GetName() != script2.GetName())
|
||||
{
|
||||
if (!strcmp(shortName, shortName2))
|
||||
{
|
||||
displayName = script.GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
displayName = shortName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
script.SetDisplayName(displayName);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptConfig::CreateTasks()
|
||||
{
|
||||
for (Script& script : m_scripts)
|
||||
{
|
||||
if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
|
||||
{
|
||||
Tokenizer tok(g_Options->GetExtensions(), ",;");
|
||||
while (const char* scriptName = tok.Next())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
|
||||
nullptr, Options::scScript, script.GetName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
daemon/extension/ScriptConfig.h
Normal file
110
daemon/extension/ScriptConfig.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SCRIPTCONFIG_H
|
||||
#define SCRIPTCONFIG_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Container.h"
|
||||
#include "Options.h"
|
||||
|
||||
class ScriptConfig
|
||||
{
|
||||
public:
|
||||
class Script
|
||||
{
|
||||
public:
|
||||
Script(const char* name, const char* location) :
|
||||
m_name(name), m_location(location), m_displayName(name) {};
|
||||
Script(Script&&) = default;
|
||||
const char* GetName() { return m_name; }
|
||||
const char* GetLocation() { return m_location; }
|
||||
void SetDisplayName(const char* displayName) { m_displayName = displayName; }
|
||||
const char* GetDisplayName() { return m_displayName; }
|
||||
bool GetPostScript() { return m_postScript; }
|
||||
void SetPostScript(bool postScript) { m_postScript = postScript; }
|
||||
bool GetScanScript() { return m_scanScript; }
|
||||
void SetScanScript(bool scanScript) { m_scanScript = scanScript; }
|
||||
bool GetQueueScript() { return m_queueScript; }
|
||||
void SetQueueScript(bool queueScript) { m_queueScript = queueScript; }
|
||||
bool GetSchedulerScript() { return m_schedulerScript; }
|
||||
void SetSchedulerScript(bool schedulerScript) { m_schedulerScript = schedulerScript; }
|
||||
bool GetFeedScript() { return m_feedScript; }
|
||||
void SetFeedScript(bool feedScript) { m_feedScript = feedScript; }
|
||||
void SetQueueEvents(const char* queueEvents) { m_queueEvents = queueEvents; }
|
||||
const char* GetQueueEvents() { return m_queueEvents; }
|
||||
void SetTaskTime(const char* taskTime) { m_taskTime = taskTime; }
|
||||
const char* GetTaskTime() { return m_taskTime; }
|
||||
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_location;
|
||||
CString m_displayName;
|
||||
bool m_postScript = false;
|
||||
bool m_scanScript = false;
|
||||
bool m_queueScript = false;
|
||||
bool m_schedulerScript = false;
|
||||
bool m_feedScript = false;
|
||||
CString m_queueEvents;
|
||||
CString m_taskTime;
|
||||
};
|
||||
|
||||
typedef std::list<Script> Scripts;
|
||||
|
||||
class ConfigTemplate
|
||||
{
|
||||
public:
|
||||
ConfigTemplate(Script&& script, const char* templ) :
|
||||
m_script(std::move(script)), m_template(templ) {}
|
||||
Script* GetScript() { return &m_script; }
|
||||
const char* GetTemplate() { return m_template; }
|
||||
|
||||
private:
|
||||
Script m_script;
|
||||
CString m_template;
|
||||
};
|
||||
|
||||
typedef std::deque<ConfigTemplate> ConfigTemplates;
|
||||
|
||||
void InitOptions();
|
||||
Scripts* GetScripts() { return &m_scripts; }
|
||||
bool LoadConfig(Options::OptEntries* optEntries);
|
||||
bool SaveConfig(Options::OptEntries* optEntries);
|
||||
bool LoadConfigTemplates(ConfigTemplates* configTemplates);
|
||||
ConfigTemplates* GetConfigTemplates() { return &m_configTemplates; }
|
||||
|
||||
private:
|
||||
Scripts m_scripts;
|
||||
ConfigTemplates m_configTemplates;
|
||||
|
||||
void InitScripts();
|
||||
void InitConfigTemplates();
|
||||
void CreateTasks();
|
||||
void LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
|
||||
void BuildScriptDisplayNames(Scripts* scripts);
|
||||
void LoadScripts(Scripts* scripts);
|
||||
bool LoadScriptFile(Script* script);
|
||||
BString<1024>BuildScriptName(const char* directory, const char* filename, bool isSubDir);
|
||||
bool ScriptExists(Scripts* scripts, const char* scriptName);
|
||||
};
|
||||
|
||||
extern ScriptConfig* g_ScriptConfig;
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,114 +14,137 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FEEDCOORDINATOR_H
|
||||
#define FEEDCOORDINATOR_H
|
||||
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Log.h"
|
||||
#include "Thread.h"
|
||||
#include "WebDownloader.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "FeedFile.h"
|
||||
#include "FeedInfo.h"
|
||||
#include "Observer.h"
|
||||
#include "Util.h"
|
||||
|
||||
|
||||
class FeedDownloader;
|
||||
|
||||
class FeedCoordinator : public Thread, public Observer, public Subject, public Debuggable
|
||||
{
|
||||
public:
|
||||
FeedCoordinator();
|
||||
virtual ~FeedCoordinator();
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
void Update(Subject* caller, void* aspect);
|
||||
void AddFeed(std::unique_ptr<FeedInfo> feedInfo) { m_feeds.push_back(std::move(feedInfo)); }
|
||||
|
||||
/* may return empty pointer on error */
|
||||
std::shared_ptr<FeedItemList> PreviewFeed(int id, const char* name, const char* url,
|
||||
const char* filter, bool backlog, bool pauseNzb, const char* category, int priority,
|
||||
int interval, const char* feedScript, int cacheTimeSec, const char* cacheId);
|
||||
|
||||
/* may return empty pointer on error */
|
||||
std::shared_ptr<FeedItemList> ViewFeed(int id);
|
||||
|
||||
void FetchFeed(int id);
|
||||
bool HasActiveDownloads();
|
||||
Feeds* GetFeeds() { return &m_feeds; }
|
||||
|
||||
protected:
|
||||
virtual void LogDebugInfo();
|
||||
|
||||
private:
|
||||
class DownloadQueueObserver: public Observer
|
||||
{
|
||||
public:
|
||||
FeedCoordinator* m_pOwner;
|
||||
virtual void Update(Subject* pCaller, void* pAspect) { m_pOwner->DownloadQueueUpdate(pCaller, pAspect); }
|
||||
FeedCoordinator* m_owner;
|
||||
virtual void Update(Subject* caller, void* aspect) { m_owner->DownloadQueueUpdate(caller, aspect); }
|
||||
};
|
||||
|
||||
class WorkStateObserver: public Observer
|
||||
{
|
||||
public:
|
||||
FeedCoordinator* m_owner;
|
||||
virtual void Update(Subject* caller, void* aspect) { m_owner->WorkStateUpdate(caller, aspect); }
|
||||
};
|
||||
|
||||
class FeedCacheItem
|
||||
{
|
||||
private:
|
||||
char* m_szUrl;
|
||||
int m_iCacheTimeSec;
|
||||
char* m_szCacheId;
|
||||
time_t m_tLastUsage;
|
||||
FeedItemInfos* m_pFeedItemInfos;
|
||||
|
||||
public:
|
||||
FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId,
|
||||
time_t tLastUsage, FeedItemInfos* pFeedItemInfos);
|
||||
~FeedCacheItem();
|
||||
const char* GetUrl() { return m_szUrl; }
|
||||
int GetCacheTimeSec() { return m_iCacheTimeSec; }
|
||||
const char* GetCacheId() { return m_szCacheId; }
|
||||
time_t GetLastUsage() { return m_tLastUsage; }
|
||||
void SetLastUsage(time_t tLastUsage) { m_tLastUsage = tLastUsage; }
|
||||
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
|
||||
FeedCacheItem(const char* url, int cacheTimeSec,const char* cacheId,
|
||||
time_t lastUsage, std::shared_ptr<FeedItemList> feedItems) :
|
||||
m_url(url), m_cacheTimeSec(cacheTimeSec), m_cacheId(cacheId),
|
||||
m_lastUsage(lastUsage), m_feedItems(feedItems) {}
|
||||
const char* GetUrl() { return m_url; }
|
||||
int GetCacheTimeSec() { return m_cacheTimeSec; }
|
||||
const char* GetCacheId() { return m_cacheId; }
|
||||
time_t GetLastUsage() { return m_lastUsage; }
|
||||
void SetLastUsage(time_t lastUsage) { m_lastUsage = lastUsage; }
|
||||
std::shared_ptr<FeedItemList> GetFeedItems() { return m_feedItems; }
|
||||
|
||||
private:
|
||||
CString m_url;
|
||||
int m_cacheTimeSec;
|
||||
CString m_cacheId;
|
||||
time_t m_lastUsage;
|
||||
std::shared_ptr<FeedItemList> m_feedItems;
|
||||
};
|
||||
|
||||
typedef std::deque<FeedCacheItem*> FeedCache;
|
||||
typedef std::list<FeedDownloader*> ActiveDownloads;
|
||||
class FilterHelper : public FeedFilterHelper
|
||||
{
|
||||
public:
|
||||
virtual std::unique_ptr<RegEx>& GetRegEx(int id);
|
||||
virtual void CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen);
|
||||
private:
|
||||
std::vector<std::unique_ptr<RegEx>> m_regExes;
|
||||
};
|
||||
|
||||
private:
|
||||
Feeds m_Feeds;
|
||||
ActiveDownloads m_ActiveDownloads;
|
||||
FeedHistory m_FeedHistory;
|
||||
Mutex m_mutexDownloads;
|
||||
DownloadQueueObserver m_DownloadQueueObserver;
|
||||
bool m_bForce;
|
||||
bool m_bSave;
|
||||
FeedCache m_FeedCache;
|
||||
typedef std::list<FeedCacheItem> FeedCache;
|
||||
typedef std::deque<FeedDownloader*> ActiveDownloads;
|
||||
|
||||
void StartFeedDownload(FeedInfo* pFeedInfo, bool bForce);
|
||||
void FeedCompleted(FeedDownloader* pFeedDownloader);
|
||||
void FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos);
|
||||
void ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos, NZBList* pAddedNZBs);
|
||||
NZBInfo* CreateNZBInfo(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo);
|
||||
void ResetHangingDownloads();
|
||||
void DownloadQueueUpdate(Subject* pCaller, void* pAspect);
|
||||
void CleanupHistory();
|
||||
void CleanupCache();
|
||||
void CheckSaveFeeds();
|
||||
Feeds m_feeds;
|
||||
ActiveDownloads m_activeDownloads;
|
||||
FeedHistory m_feedHistory;
|
||||
Mutex m_downloadsMutex;
|
||||
DownloadQueueObserver m_downloadQueueObserver;
|
||||
WorkStateObserver m_workStateObserver;
|
||||
bool m_force = false;
|
||||
bool m_save = false;
|
||||
FeedCache m_feedCache;
|
||||
ConditionVar m_waitCond;
|
||||
bool m_wokenUp = false;
|
||||
|
||||
protected:
|
||||
virtual void LogDebugInfo();
|
||||
|
||||
public:
|
||||
FeedCoordinator();
|
||||
virtual ~FeedCoordinator();
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
void Update(Subject* pCaller, void* pAspect);
|
||||
void AddFeed(FeedInfo* pFeedInfo);
|
||||
bool PreviewFeed(const char* szName, const char* szUrl, const char* szFilter,
|
||||
bool bPauseNzb, const char* szCategory, int iPriority,
|
||||
int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos);
|
||||
bool ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos);
|
||||
void FetchFeed(int iID);
|
||||
bool HasActiveDownloads();
|
||||
Feeds* GetFeeds() { return &m_Feeds; }
|
||||
void StartFeedDownload(FeedInfo* feedInfo, bool force);
|
||||
void FeedCompleted(FeedDownloader* feedDownloader);
|
||||
void FilterFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
|
||||
std::vector<std::unique_ptr<NzbInfo>> ProcessFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
|
||||
std::unique_ptr<NzbInfo> CreateNzbInfo(FeedInfo* feedInfo, FeedItemInfo& feedItemInfo);
|
||||
void ResetHangingDownloads();
|
||||
void DownloadQueueUpdate(Subject* caller, void* aspect);
|
||||
void CleanupHistory();
|
||||
void CleanupCache();
|
||||
void CheckSaveFeeds();
|
||||
std::unique_ptr<FeedFile> parseFeed(FeedInfo* feedInfo);
|
||||
void SchedulerNextUpdate(FeedInfo* feedInfo, bool success);
|
||||
void WorkStateUpdate(Subject* caller, void* aspect);
|
||||
};
|
||||
|
||||
extern FeedCoordinator* g_FeedCoordinator;
|
||||
|
||||
class FeedDownloader : public WebDownloader
|
||||
{
|
||||
private:
|
||||
FeedInfo* m_pFeedInfo;
|
||||
|
||||
public:
|
||||
void SetFeedInfo(FeedInfo* pFeedInfo) { m_pFeedInfo = pFeedInfo; }
|
||||
FeedInfo* GetFeedInfo() { return m_pFeedInfo; }
|
||||
void SetFeedInfo(FeedInfo* feedInfo) { m_feedInfo = feedInfo; }
|
||||
FeedInfo* GetFeedInfo() { return m_feedInfo; }
|
||||
|
||||
private:
|
||||
FeedInfo* m_feedInfo;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,36 +14,10 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <list>
|
||||
#ifdef WIN32
|
||||
#include <comutil.h>
|
||||
#import <msxml.tlb> named_guids
|
||||
using namespace MSXML;
|
||||
#else
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <libxml/xmlerror.h>
|
||||
#include <libxml/entities.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "FeedFile.h"
|
||||
#include "Log.h"
|
||||
@@ -51,51 +25,28 @@ using namespace MSXML;
|
||||
#include "Options.h"
|
||||
#include "Util.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
|
||||
FeedFile::FeedFile(const char* szFileName)
|
||||
FeedFile::FeedFile(const char* fileName, const char* infoName) :
|
||||
m_fileName(fileName), m_infoName(infoName)
|
||||
{
|
||||
debug("Creating FeedFile");
|
||||
debug("Creating FeedFile");
|
||||
|
||||
m_szFileName = strdup(szFileName);
|
||||
m_pFeedItemInfos = new FeedItemInfos();
|
||||
m_pFeedItemInfos->Retain();
|
||||
m_feedItems = std::make_unique<FeedItemList>();
|
||||
|
||||
#ifndef WIN32
|
||||
m_pFeedItemInfo = NULL;
|
||||
m_szTagContent = NULL;
|
||||
m_iTagContentLen = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
FeedFile::~FeedFile()
|
||||
{
|
||||
debug("Destroying FeedFile");
|
||||
|
||||
// Cleanup
|
||||
free(m_szFileName);
|
||||
m_pFeedItemInfos->Release();
|
||||
|
||||
#ifndef WIN32
|
||||
delete m_pFeedItemInfo;
|
||||
free(m_szTagContent);
|
||||
m_feedItemInfo = nullptr;
|
||||
m_tagContent.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FeedFile::LogDebugInfo()
|
||||
{
|
||||
info(" FeedFile %s", m_szFileName);
|
||||
info(" FeedFile %s", *m_fileName);
|
||||
}
|
||||
|
||||
void FeedFile::AddItem(FeedItemInfo* pFeedItemInfo)
|
||||
void FeedFile::ParseSubject(FeedItemInfo& feedItemInfo)
|
||||
{
|
||||
m_pFeedItemInfos->Add(pFeedItemInfo);
|
||||
}
|
||||
|
||||
void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
|
||||
{
|
||||
// if title has quatation marks we use only part within quatation marks
|
||||
char* p = (char*)pFeedItemInfo->GetTitle();
|
||||
// if title has quatation marks we use only part within quatation marks
|
||||
char* p = (char*)feedItemInfo.GetTitle();
|
||||
char* start = strchr(p, '\"');
|
||||
if (start)
|
||||
{
|
||||
@@ -107,9 +58,7 @@ void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
|
||||
char* point = strchr(start + 1, '.');
|
||||
if (point && point < end)
|
||||
{
|
||||
char* filename = (char*)malloc(len + 1);
|
||||
strncpy(filename, start, len);
|
||||
filename[len] = '\0';
|
||||
CString filename(start, len);
|
||||
|
||||
char* ext = strrchr(filename, '.');
|
||||
if (ext && !strcasecmp(ext, ".par2"))
|
||||
@@ -117,81 +66,87 @@ void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
|
||||
*ext = '\0';
|
||||
}
|
||||
|
||||
pFeedItemInfo->SetFilename(filename);
|
||||
free(filename);
|
||||
feedItemInfo.SetFilename(filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pFeedItemInfo->SetFilename(pFeedItemInfo->GetTitle());
|
||||
feedItemInfo.SetFilename(feedItemInfo.GetTitle());
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
FeedFile* FeedFile::Create(const char* szFileName)
|
||||
bool FeedFile::Parse()
|
||||
{
|
||||
CoInitialize(NULL);
|
||||
CoInitialize(nullptr);
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
MSXML::IXMLDOMDocumentPtr doc;
|
||||
hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the XML document file...
|
||||
// Load the XML document file...
|
||||
doc->put_resolveExternals(VARIANT_FALSE);
|
||||
doc->put_validateOnParse(VARIANT_FALSE);
|
||||
doc->put_async(VARIANT_FALSE);
|
||||
|
||||
// filename needs to be properly encoded
|
||||
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
|
||||
EncodeURL(szFileName, szURL);
|
||||
debug("url=\"%s\"", szURL);
|
||||
_variant_t v(szURL);
|
||||
free(szURL);
|
||||
_variant_t vFilename(*WString(m_fileName));
|
||||
|
||||
// 1. first trying to load via filename without URL-encoding (certain charaters doesn't work when encoded)
|
||||
VARIANT_BOOL success = doc->load(vFilename);
|
||||
if (success == VARIANT_FALSE)
|
||||
{
|
||||
// 2. now trying filename encoded as URL
|
||||
char url[2048];
|
||||
EncodeUrl(m_fileName, url, 2048);
|
||||
debug("url=\"%s\"", url);
|
||||
_variant_t vUrl(url);
|
||||
|
||||
success = doc->load(vUrl);
|
||||
}
|
||||
|
||||
VARIANT_BOOL success = doc->load(v);
|
||||
if (success == VARIANT_FALSE)
|
||||
{
|
||||
_bstr_t r(doc->GetparseError()->reason);
|
||||
const char* szErrMsg = r;
|
||||
error("Error parsing rss feed: %s", szErrMsg);
|
||||
return NULL;
|
||||
const char* errMsg = r;
|
||||
error("Error parsing rss feed %s: %s", *m_infoName, errMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
FeedFile* pFile = new FeedFile(szFileName);
|
||||
if (!pFile->ParseFeed(doc))
|
||||
{
|
||||
delete pFile;
|
||||
pFile = NULL;
|
||||
}
|
||||
bool ok = ParseFeed(doc);
|
||||
|
||||
return pFile;
|
||||
return ok;
|
||||
}
|
||||
|
||||
void FeedFile::EncodeURL(const char* szFilename, char* szURL)
|
||||
void FeedFile::EncodeUrl(const char* filename, char* url, int bufLen)
|
||||
{
|
||||
while (char ch = *szFilename++)
|
||||
WString widefilename(filename);
|
||||
|
||||
char* end = url + bufLen;
|
||||
for (wchar_t* p = widefilename; *p && url < end - 3; p++)
|
||||
{
|
||||
wchar_t ch = *p;
|
||||
if (('0' <= ch && ch <= '9') ||
|
||||
('a' <= ch && ch <= 'z') ||
|
||||
('A' <= ch && ch <= 'Z') )
|
||||
('A' <= ch && ch <= 'Z') ||
|
||||
ch == '-' || ch == '.' || ch == '_' || ch == '~')
|
||||
{
|
||||
*szURL++ = ch;
|
||||
*url++ = (char)ch;
|
||||
}
|
||||
else
|
||||
{
|
||||
*szURL++ = '%';
|
||||
int a = ch >> 4;
|
||||
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
|
||||
*url++ = '%';
|
||||
uint32 a = (uint32)ch >> 4;
|
||||
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
|
||||
a = ch & 0xF;
|
||||
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
|
||||
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
|
||||
}
|
||||
}
|
||||
*szURL = NULL;
|
||||
*url = '\0';
|
||||
}
|
||||
|
||||
bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
@@ -204,13 +159,13 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
{
|
||||
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
|
||||
|
||||
FeedItemInfo* pFeedItemInfo = new FeedItemInfo();
|
||||
AddItem(pFeedItemInfo);
|
||||
m_feedItems->emplace_back();
|
||||
FeedItemInfo& feedItemInfo = m_feedItems->back();
|
||||
|
||||
MSXML::IXMLDOMNodePtr tag;
|
||||
MSXML::IXMLDOMNodePtr attr;
|
||||
|
||||
// <title>Debian 6</title>
|
||||
|
||||
// <title>Debian 6</title>
|
||||
tag = node->selectSingleNode("title");
|
||||
if (!tag)
|
||||
{
|
||||
@@ -218,8 +173,8 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
return false;
|
||||
}
|
||||
_bstr_t title(tag->Gettext());
|
||||
pFeedItemInfo->SetTitle(title);
|
||||
ParseSubject(pFeedItemInfo);
|
||||
feedItemInfo.SetTitle(title);
|
||||
ParseSubject(feedItemInfo);
|
||||
|
||||
// <pubDate>Wed, 26 Jun 2013 00:02:54 -0600</pubDate>
|
||||
tag = node->selectSingleNode("pubDate");
|
||||
@@ -229,7 +184,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
time_t unixtime = WebUtil::ParseRfc822DateTime(time);
|
||||
if (unixtime > 0)
|
||||
{
|
||||
pFeedItemInfo->SetTime(unixtime);
|
||||
feedItemInfo.SetTime(unixtime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,15 +193,20 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
if (tag)
|
||||
{
|
||||
_bstr_t category(tag->Gettext());
|
||||
pFeedItemInfo->SetCategory(category);
|
||||
feedItemInfo.SetCategory(category);
|
||||
}
|
||||
|
||||
// <description>long text</description>
|
||||
tag = node->selectSingleNode("description");
|
||||
if (tag)
|
||||
{
|
||||
_bstr_t description(tag->Gettext());
|
||||
pFeedItemInfo->SetDescription(description);
|
||||
_bstr_t bdescription(tag->Gettext());
|
||||
// cleanup CDATA
|
||||
CString description = (const char*)bdescription;
|
||||
WebUtil::XmlStripTags(description);
|
||||
WebUtil::XmlDecode(description);
|
||||
WebUtil::XmlRemoveEntities(description);
|
||||
feedItemInfo.SetDescription(description);
|
||||
}
|
||||
|
||||
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
|
||||
@@ -257,19 +217,19 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t url(attr->Gettext());
|
||||
pFeedItemInfo->SetUrl(url);
|
||||
feedItemInfo.SetUrl(url);
|
||||
}
|
||||
|
||||
attr = tag->Getattributes()->getNamedItem("length");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t size(attr->Gettext());
|
||||
long long lSize = atoll(size);
|
||||
pFeedItemInfo->SetSize(lSize);
|
||||
_bstr_t bsize(attr->Gettext());
|
||||
int64 size = atoll(bsize);
|
||||
feedItemInfo.SetSize(size);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pFeedItemInfo->GetUrl())
|
||||
if (!feedItemInfo.GetUrl())
|
||||
{
|
||||
// <link>https://nzb.org/fetch/334534ce/4364564564</link>
|
||||
tag = node->selectSingleNode("link");
|
||||
@@ -279,81 +239,107 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
return false;
|
||||
}
|
||||
_bstr_t link(tag->Gettext());
|
||||
pFeedItemInfo->SetUrl(link);
|
||||
feedItemInfo.SetUrl(link);
|
||||
}
|
||||
|
||||
|
||||
// newznab special
|
||||
|
||||
//<newznab:attr name="size" value="5423523453534" />
|
||||
if (pFeedItemInfo->GetSize() == 0)
|
||||
if (feedItemInfo.GetSize() == 0)
|
||||
{
|
||||
tag = node->selectSingleNode("newznab:attr[@name='size']");
|
||||
tag = node->selectSingleNode("newznab:attr[@name='size'] | nZEDb:attr[@name='size']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t size(attr->Gettext());
|
||||
long long lSize = atoll(size);
|
||||
pFeedItemInfo->SetSize(lSize);
|
||||
_bstr_t bsize(attr->Gettext());
|
||||
int64 size = atoll(bsize);
|
||||
feedItemInfo.SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="imdb" value="1588173"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='imdb']");
|
||||
tag = node->selectSingleNode("newznab:attr[@name='imdb'] | nZEDb:attr[@name='imdb']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
int iVal = atoi(val);
|
||||
pFeedItemInfo->SetImdbId(iVal);
|
||||
_bstr_t bval(attr->Gettext());
|
||||
int val = atoi(bval);
|
||||
feedItemInfo.SetImdbId(val);
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="rageid" value="33877"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='rageid']");
|
||||
tag = node->selectSingleNode("newznab:attr[@name='rageid'] | nZEDb:attr[@name='rageid']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
int iVal = atoi(val);
|
||||
pFeedItemInfo->SetRageId(iVal);
|
||||
_bstr_t bval(attr->Gettext());
|
||||
int val = atoi(bval);
|
||||
feedItemInfo.SetRageId(val);
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="tdvdbid" value="33877"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='tvdbid'] | nZEDb:attr[@name='tvdbid']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t bval(attr->Gettext());
|
||||
int val = atoi(bval);
|
||||
feedItemInfo.SetTvdbId(val);
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="tvmazeid" value="33877"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='tvmazeid'] | nZEDb:attr[@name='tvmazeid']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t bval(attr->Gettext());
|
||||
int val = atoi(bval);
|
||||
feedItemInfo.SetTvmazeId(val);
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="episode" value="E09"/>
|
||||
//<newznab:attr name="episode" value="9"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='episode']");
|
||||
tag = node->selectSingleNode("newznab:attr[@name='episode'] | nZEDb:attr[@name='episode']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
pFeedItemInfo->SetEpisode(val);
|
||||
feedItemInfo.SetEpisode(val);
|
||||
}
|
||||
}
|
||||
|
||||
//<newznab:attr name="season" value="S03"/>
|
||||
//<newznab:attr name="season" value="3"/>
|
||||
tag = node->selectSingleNode("newznab:attr[@name='season']");
|
||||
tag = node->selectSingleNode("newznab:attr[@name='season'] | nZEDb:attr[@name='season']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
pFeedItemInfo->SetSeason(val);
|
||||
feedItemInfo.SetSeason(val);
|
||||
}
|
||||
}
|
||||
|
||||
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr");
|
||||
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr | nZEDb:attr");
|
||||
for (int i = 0; i < itemList->Getlength(); i++)
|
||||
{
|
||||
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
|
||||
@@ -361,9 +347,9 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
MSXML::IXMLDOMNodePtr value = node->Getattributes()->getNamedItem("value");
|
||||
if (name && value)
|
||||
{
|
||||
_bstr_t name(name->Gettext());
|
||||
_bstr_t val(value->Gettext());
|
||||
pFeedItemInfo->GetAttributes()->Add(name, val);
|
||||
_bstr_t bname(name->Gettext());
|
||||
_bstr_t bval(value->Gettext());
|
||||
feedItemInfo.GetAttributes()->emplace_back(bname, bval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,10 +358,12 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
|
||||
#else
|
||||
|
||||
FeedFile* FeedFile::Create(const char* szFileName)
|
||||
bool FeedFile::Parse()
|
||||
{
|
||||
FeedFile* pFile = new FeedFile(szFileName);
|
||||
|
||||
#ifdef DISABLE_LIBXML2
|
||||
error("Could not parse rss feed, program was compiled without libxml2 support");
|
||||
return false;
|
||||
#else
|
||||
xmlSAXHandler SAX_handler = {0};
|
||||
SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
|
||||
SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
|
||||
@@ -383,126 +371,137 @@ FeedFile* FeedFile::Create(const char* szFileName)
|
||||
SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
|
||||
SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
|
||||
|
||||
pFile->m_bIgnoreNextError = false;
|
||||
m_ignoreNextError = false;
|
||||
|
||||
int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName);
|
||||
|
||||
if (ret != 0)
|
||||
int ret = xmlSAXUserParseFile(&SAX_handler, this, m_fileName);
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
error("Failed to parse rss feed");
|
||||
delete pFile;
|
||||
pFile = NULL;
|
||||
error("Failed to parse rss feed %s", *m_infoName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return pFile;
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FeedFile::Parse_StartElement(const char *name, const char **atts)
|
||||
{
|
||||
ResetTagContent();
|
||||
|
||||
|
||||
if (!strcmp("item", name))
|
||||
{
|
||||
delete m_pFeedItemInfo;
|
||||
m_pFeedItemInfo = new FeedItemInfo();
|
||||
m_feedItems->emplace_back();
|
||||
m_feedItemInfo = &m_feedItems->back();
|
||||
}
|
||||
else if (!strcmp("enclosure", name) && m_pFeedItemInfo)
|
||||
else if (!strcmp("enclosure", name) && m_feedItemInfo)
|
||||
{
|
||||
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
|
||||
for (; *atts; atts+=2)
|
||||
{
|
||||
if (!strcmp("url", atts[0]))
|
||||
{
|
||||
char* szUrl = strdup(atts[1]);
|
||||
WebUtil::XmlDecode(szUrl);
|
||||
m_pFeedItemInfo->SetUrl(szUrl);
|
||||
free(szUrl);
|
||||
CString url = atts[1];
|
||||
WebUtil::XmlDecode(url);
|
||||
m_feedItemInfo->SetUrl(url);
|
||||
}
|
||||
else if (!strcmp("length", atts[0]))
|
||||
{
|
||||
long long lSize = atoll(atts[1]);
|
||||
m_pFeedItemInfo->SetSize(lSize);
|
||||
int64 size = atoll(atts[1]);
|
||||
m_feedItemInfo->SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_pFeedItemInfo && !strcmp("newznab:attr", name) &&
|
||||
else if (m_feedItemInfo &&
|
||||
(!strcmp("newznab:attr", name) || !strcmp("nZEDb:attr", name)) &&
|
||||
atts[0] && atts[1] && atts[2] && atts[3] &&
|
||||
!strcmp("name", atts[0]) && !strcmp("value", atts[2]))
|
||||
{
|
||||
m_pFeedItemInfo->GetAttributes()->Add(atts[1], atts[3]);
|
||||
m_feedItemInfo->GetAttributes()->emplace_back(atts[1], atts[3]);
|
||||
|
||||
//<newznab:attr name="size" value="5423523453534" />
|
||||
if (m_pFeedItemInfo->GetSize() == 0 &&
|
||||
if (m_feedItemInfo->GetSize() == 0 &&
|
||||
!strcmp("size", atts[1]))
|
||||
{
|
||||
long long lSize = atoll(atts[3]);
|
||||
m_pFeedItemInfo->SetSize(lSize);
|
||||
int64 size = atoll(atts[3]);
|
||||
m_feedItemInfo->SetSize(size);
|
||||
}
|
||||
|
||||
//<newznab:attr name="imdb" value="1588173"/>
|
||||
else if (!strcmp("imdb", atts[1]))
|
||||
{
|
||||
m_pFeedItemInfo->SetImdbId(atoi(atts[3]));
|
||||
m_feedItemInfo->SetImdbId(atoi(atts[3]));
|
||||
}
|
||||
|
||||
//<newznab:attr name="rageid" value="33877"/>
|
||||
else if (!strcmp("rageid", atts[1]))
|
||||
{
|
||||
m_pFeedItemInfo->SetRageId(atoi(atts[3]));
|
||||
m_feedItemInfo->SetRageId(atoi(atts[3]));
|
||||
}
|
||||
|
||||
//<newznab:attr name="tvdbid" value="33877"/>
|
||||
else if (!strcmp("tvdbid", atts[1]))
|
||||
{
|
||||
m_feedItemInfo->SetTvdbId(atoi(atts[3]));
|
||||
}
|
||||
|
||||
//<newznab:attr name="tvmazeid" value="33877"/>
|
||||
else if (!strcmp("tvmazeid", atts[1]))
|
||||
{
|
||||
m_feedItemInfo->SetTvmazeId(atoi(atts[3]));
|
||||
}
|
||||
|
||||
//<newznab:attr name="episode" value="E09"/>
|
||||
//<newznab:attr name="episode" value="9"/>
|
||||
else if (!strcmp("episode", atts[1]))
|
||||
{
|
||||
m_pFeedItemInfo->SetEpisode(atts[3]);
|
||||
m_feedItemInfo->SetEpisode(atts[3]);
|
||||
}
|
||||
|
||||
//<newznab:attr name="season" value="S03"/>
|
||||
//<newznab:attr name="season" value="3"/>
|
||||
else if (!strcmp("season", atts[1]))
|
||||
{
|
||||
m_pFeedItemInfo->SetSeason(atts[3]);
|
||||
m_feedItemInfo->SetSeason(atts[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedFile::Parse_EndElement(const char *name)
|
||||
{
|
||||
if (!strcmp("item", name))
|
||||
if (!strcmp("title", name) && m_feedItemInfo)
|
||||
{
|
||||
// Close the file element, add the new file to file-list
|
||||
AddItem(m_pFeedItemInfo);
|
||||
m_pFeedItemInfo = NULL;
|
||||
}
|
||||
else if (!strcmp("title", name) && m_pFeedItemInfo)
|
||||
{
|
||||
m_pFeedItemInfo->SetTitle(m_szTagContent);
|
||||
m_feedItemInfo->SetTitle(m_tagContent);
|
||||
ResetTagContent();
|
||||
ParseSubject(m_pFeedItemInfo);
|
||||
ParseSubject(*m_feedItemInfo);
|
||||
}
|
||||
else if (!strcmp("link", name) && m_pFeedItemInfo &&
|
||||
(!m_pFeedItemInfo->GetUrl() || strlen(m_pFeedItemInfo->GetUrl()) == 0))
|
||||
else if (!strcmp("link", name) && m_feedItemInfo &&
|
||||
(!m_feedItemInfo->GetUrl() || strlen(m_feedItemInfo->GetUrl()) == 0))
|
||||
{
|
||||
m_pFeedItemInfo->SetUrl(m_szTagContent);
|
||||
m_feedItemInfo->SetUrl(m_tagContent);
|
||||
ResetTagContent();
|
||||
}
|
||||
else if (!strcmp("category", name) && m_pFeedItemInfo)
|
||||
else if (!strcmp("category", name) && m_feedItemInfo)
|
||||
{
|
||||
m_pFeedItemInfo->SetCategory(m_szTagContent);
|
||||
m_feedItemInfo->SetCategory(m_tagContent);
|
||||
ResetTagContent();
|
||||
}
|
||||
else if (!strcmp("description", name) && m_pFeedItemInfo)
|
||||
else if (!strcmp("description", name) && m_feedItemInfo)
|
||||
{
|
||||
m_pFeedItemInfo->SetDescription(m_szTagContent);
|
||||
// cleanup CDATA
|
||||
CString description = *m_tagContent;
|
||||
WebUtil::XmlStripTags(description);
|
||||
WebUtil::XmlDecode(description);
|
||||
WebUtil::XmlRemoveEntities(description);
|
||||
m_feedItemInfo->SetDescription(description);
|
||||
ResetTagContent();
|
||||
}
|
||||
else if (!strcmp("pubDate", name) && m_pFeedItemInfo)
|
||||
else if (!strcmp("pubDate", name) && m_feedItemInfo)
|
||||
{
|
||||
time_t unixtime = WebUtil::ParseRfc822DateTime(m_szTagContent);
|
||||
time_t unixtime = WebUtil::ParseRfc822DateTime(m_tagContent);
|
||||
if (unixtime > 0)
|
||||
{
|
||||
m_pFeedItemInfo->SetTime(unixtime);
|
||||
m_feedItemInfo->SetTime(unixtime);
|
||||
}
|
||||
ResetTagContent();
|
||||
}
|
||||
@@ -510,33 +509,28 @@ void FeedFile::Parse_EndElement(const char *name)
|
||||
|
||||
void FeedFile::Parse_Content(const char *buf, int len)
|
||||
{
|
||||
m_szTagContent = (char*)realloc(m_szTagContent, m_iTagContentLen + len + 1);
|
||||
strncpy(m_szTagContent + m_iTagContentLen, buf, len);
|
||||
m_iTagContentLen += len;
|
||||
m_szTagContent[m_iTagContentLen] = '\0';
|
||||
m_tagContent.Append(buf, len);
|
||||
}
|
||||
|
||||
void FeedFile::ResetTagContent()
|
||||
{
|
||||
free(m_szTagContent);
|
||||
m_szTagContent = NULL;
|
||||
m_iTagContentLen = 0;
|
||||
m_tagContent.Clear();
|
||||
}
|
||||
|
||||
void FeedFile::SAX_StartElement(FeedFile* pFile, const char *name, const char **atts)
|
||||
void FeedFile::SAX_StartElement(FeedFile* file, const char *name, const char **atts)
|
||||
{
|
||||
pFile->Parse_StartElement(name, atts);
|
||||
file->Parse_StartElement(name, atts);
|
||||
}
|
||||
|
||||
void FeedFile::SAX_EndElement(FeedFile* pFile, const char *name)
|
||||
void FeedFile::SAX_EndElement(FeedFile* file, const char *name)
|
||||
{
|
||||
pFile->Parse_EndElement(name);
|
||||
file->Parse_EndElement(name);
|
||||
}
|
||||
|
||||
void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
|
||||
void FeedFile::SAX_characters(FeedFile* file, const char * xmlstr, int len)
|
||||
{
|
||||
char* str = (char*)xmlstr;
|
||||
|
||||
|
||||
// trim starting blanks
|
||||
int off = 0;
|
||||
for (int i = 0; i < len; i++)
|
||||
@@ -551,9 +545,9 @@ void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int newlen = len - off;
|
||||
|
||||
|
||||
// trim ending blanks
|
||||
for (int i = len - 1; i >= off; i--)
|
||||
{
|
||||
@@ -567,43 +561,47 @@ void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (newlen > 0)
|
||||
{
|
||||
// interpret tag content
|
||||
pFile->Parse_Content(str + off, newlen);
|
||||
file->Parse_Content(str + off, newlen);
|
||||
}
|
||||
}
|
||||
|
||||
void* FeedFile::SAX_getEntity(FeedFile* pFile, const char * name)
|
||||
void* FeedFile::SAX_getEntity(FeedFile* file, const char * name)
|
||||
{
|
||||
#ifdef DISABLE_LIBXML2
|
||||
void* e = nullptr;
|
||||
#else
|
||||
xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
|
||||
#endif
|
||||
if (!e)
|
||||
{
|
||||
warn("entity not found");
|
||||
pFile->m_bIgnoreNextError = true;
|
||||
file->m_ignoreNextError = true;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void FeedFile::SAX_error(FeedFile* pFile, const char *msg, ...)
|
||||
void FeedFile::SAX_error(FeedFile* file, const char *msg, ...)
|
||||
{
|
||||
if (pFile->m_bIgnoreNextError)
|
||||
if (file->m_ignoreNextError)
|
||||
{
|
||||
pFile->m_bIgnoreNextError = false;
|
||||
file->m_ignoreNextError = false;
|
||||
return;
|
||||
}
|
||||
|
||||
va_list argp;
|
||||
va_start(argp, msg);
|
||||
char szErrMsg[1024];
|
||||
vsnprintf(szErrMsg, sizeof(szErrMsg), msg, argp);
|
||||
szErrMsg[1024-1] = '\0';
|
||||
va_end(argp);
|
||||
|
||||
va_list argp;
|
||||
va_start(argp, msg);
|
||||
char errMsg[1024];
|
||||
vsnprintf(errMsg, sizeof(errMsg), msg, argp);
|
||||
errMsg[1024-1] = '\0';
|
||||
va_end(argp);
|
||||
|
||||
// remove trailing CRLF
|
||||
for (char* pend = szErrMsg + strlen(szErrMsg) - 1; pend >= szErrMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
|
||||
error("Error parsing rss feed: %s", szErrMsg);
|
||||
for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
|
||||
error("Error parsing rss feed %s: %s", *file->m_infoName, errMsg);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,57 +14,49 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FEEDFILE_H
|
||||
#define FEEDFILE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "NString.h"
|
||||
#include "FeedInfo.h"
|
||||
|
||||
class FeedFile
|
||||
{
|
||||
private:
|
||||
FeedItemInfos* m_pFeedItemInfos;
|
||||
char* m_szFileName;
|
||||
|
||||
FeedFile(const char* szFileName);
|
||||
void AddItem(FeedItemInfo* pFeedItemInfo);
|
||||
void ParseSubject(FeedItemInfo* pFeedItemInfo);
|
||||
#ifdef WIN32
|
||||
bool ParseFeed(IUnknown* nzb);
|
||||
static void EncodeURL(const char* szFilename, char* szURL);
|
||||
#else
|
||||
FeedItemInfo* m_pFeedItemInfo;
|
||||
char* m_szTagContent;
|
||||
int m_iTagContentLen;
|
||||
bool m_bIgnoreNextError;
|
||||
|
||||
static void SAX_StartElement(FeedFile* pFile, const char *name, const char **atts);
|
||||
static void SAX_EndElement(FeedFile* pFile, const char *name);
|
||||
static void SAX_characters(FeedFile* pFile, const char * xmlstr, int len);
|
||||
static void* SAX_getEntity(FeedFile* pFile, const char * name);
|
||||
static void SAX_error(FeedFile* pFile, const char *msg, ...);
|
||||
void Parse_StartElement(const char *name, const char **atts);
|
||||
void Parse_EndElement(const char *name);
|
||||
void Parse_Content(const char *buf, int len);
|
||||
void ResetTagContent();
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual ~FeedFile();
|
||||
static FeedFile* Create(const char* szFileName);
|
||||
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
|
||||
FeedFile(const char* fileName, const char* infoName);
|
||||
bool Parse();
|
||||
std::unique_ptr<FeedItemList> DetachFeedItems() { return std::move(m_feedItems); }
|
||||
|
||||
void LogDebugInfo();
|
||||
void LogDebugInfo();
|
||||
|
||||
private:
|
||||
std::unique_ptr<FeedItemList> m_feedItems;
|
||||
CString m_fileName;
|
||||
CString m_infoName;
|
||||
|
||||
void ParseSubject(FeedItemInfo& feedItemInfo);
|
||||
#ifdef WIN32
|
||||
bool ParseFeed(IUnknown* nzb);
|
||||
static void EncodeUrl(const char* filename, char* url, int bufLen);
|
||||
#else
|
||||
FeedItemInfo* m_feedItemInfo;
|
||||
StringBuilder m_tagContent;
|
||||
bool m_ignoreNextError;
|
||||
|
||||
static void SAX_StartElement(FeedFile* file, const char *name, const char **atts);
|
||||
static void SAX_EndElement(FeedFile* file, const char *name);
|
||||
static void SAX_characters(FeedFile* file, const char * xmlstr, int len);
|
||||
static void* SAX_getEntity(FeedFile* file, const char * name);
|
||||
static void SAX_error(FeedFile* file, const char *msg, ...);
|
||||
void Parse_StartElement(const char *name, const char **atts);
|
||||
void Parse_EndElement(const char *name);
|
||||
void Parse_Content(const char *buf, int len);
|
||||
void ResetTagContent();
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,26 +14,26 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FEEDFILTER_H
|
||||
#define FEEDFILTER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "FeedInfo.h"
|
||||
#include "Util.h"
|
||||
|
||||
class FeedFilter
|
||||
{
|
||||
public:
|
||||
FeedFilter(const char* filter);
|
||||
void Match(FeedItemInfo& feedItemInfo);
|
||||
|
||||
private:
|
||||
typedef std::deque<char*> RefValues;
|
||||
typedef std::vector<CString> RefValues;
|
||||
|
||||
enum ETermCommand
|
||||
{
|
||||
@@ -48,42 +48,42 @@ private:
|
||||
fcClosingBrace,
|
||||
fcOrOperator
|
||||
};
|
||||
|
||||
|
||||
class Term
|
||||
{
|
||||
private:
|
||||
bool m_bPositive;
|
||||
char* m_szField;
|
||||
ETermCommand m_eCommand;
|
||||
char* m_szParam;
|
||||
long long m_iIntParam;
|
||||
double m_fFloatParam;
|
||||
bool m_bFloat;
|
||||
RegEx* m_pRegEx;
|
||||
RefValues* m_pRefValues;
|
||||
|
||||
bool GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo,
|
||||
const char** StrValue, long long* IntValue);
|
||||
bool ParseParam(const char* szField, const char* szParam);
|
||||
bool ParseSizeParam(const char* szParam);
|
||||
bool ParseAgeParam(const char* szParam);
|
||||
bool ParseNumericParam(const char* szParam);
|
||||
bool MatchValue(const char* szStrValue, long long iIntValue);
|
||||
bool MatchText(const char* szStrValue);
|
||||
bool MatchRegex(const char* szStrValue);
|
||||
void FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset);
|
||||
void FillRegExRefValues(const char* szStrValue, RegEx* pRegEx);
|
||||
|
||||
public:
|
||||
Term();
|
||||
~Term();
|
||||
void SetRefValues(RefValues* pRefValues) { m_pRefValues = pRefValues; }
|
||||
bool Compile(char* szToken);
|
||||
bool Match(FeedItemInfo* pFeedItemInfo);
|
||||
ETermCommand GetCommand() { return m_eCommand; }
|
||||
Term() {}
|
||||
Term(Term&&) = delete; // catch performance issues
|
||||
void SetRefValues(RefValues* refValues) { m_refValues = refValues; }
|
||||
bool Compile(char* token);
|
||||
bool Match(FeedItemInfo& feedItemInfo);
|
||||
ETermCommand GetCommand() { return m_command; }
|
||||
|
||||
private:
|
||||
bool m_positive;
|
||||
CString m_field;
|
||||
ETermCommand m_command;
|
||||
CString m_param;
|
||||
int64 m_intParam = 0;
|
||||
double m_floatParam = 0.0;
|
||||
bool m_float = false;
|
||||
std::unique_ptr<RegEx> m_regEx;
|
||||
RefValues* m_refValues = nullptr;
|
||||
|
||||
bool GetFieldData(const char* field, FeedItemInfo* feedItemInfo,
|
||||
const char** StrValue, int64* IntValue);
|
||||
bool ParseParam(const char* field, const char* param);
|
||||
bool ParseSizeParam(const char* param);
|
||||
bool ParseAgeParam(const char* param);
|
||||
bool ParseNumericParam(const char* param);
|
||||
bool MatchValue(const char* strValue, int64 intValue);
|
||||
bool MatchText(const char* strValue);
|
||||
bool MatchRegex(const char* strValue);
|
||||
void FillWildMaskRefValues(const char* strValue, WildMask* mask, int refOffset);
|
||||
void FillRegExRefValues(const char* strValue, RegEx* regEx);
|
||||
};
|
||||
|
||||
typedef std::deque<Term*> TermList;
|
||||
typedef std::deque<Term> TermList;
|
||||
|
||||
enum ERuleCommand
|
||||
{
|
||||
@@ -96,91 +96,93 @@ private:
|
||||
|
||||
class Rule
|
||||
{
|
||||
private:
|
||||
bool m_bIsValid;
|
||||
ERuleCommand m_eCommand;
|
||||
char* m_szCategory;
|
||||
int m_iPriority;
|
||||
int m_iAddPriority;
|
||||
bool m_bPause;
|
||||
int m_iDupeScore;
|
||||
int m_iAddDupeScore;
|
||||
char* m_szDupeKey;
|
||||
char* m_szAddDupeKey;
|
||||
EDupeMode m_eDupeMode;
|
||||
char* m_szSeries;
|
||||
char* m_szRageId;
|
||||
bool m_bHasCategory;
|
||||
bool m_bHasPriority;
|
||||
bool m_bHasAddPriority;
|
||||
bool m_bHasPause;
|
||||
bool m_bHasDupeScore;
|
||||
bool m_bHasAddDupeScore;
|
||||
bool m_bHasDupeKey;
|
||||
bool m_bHasAddDupeKey;
|
||||
bool m_bHasDupeMode;
|
||||
bool m_bPatCategory;
|
||||
bool m_bPatDupeKey;
|
||||
bool m_bPatAddDupeKey;
|
||||
bool m_bHasSeries;
|
||||
bool m_bHasRageId;
|
||||
char* m_szPatCategory;
|
||||
char* m_szPatDupeKey;
|
||||
char* m_szPatAddDupeKey;
|
||||
TermList m_Terms;
|
||||
RefValues m_RefValues;
|
||||
|
||||
char* CompileCommand(char* szRule);
|
||||
char* CompileOptions(char* szRule);
|
||||
bool CompileTerm(char* szTerm);
|
||||
bool MatchExpression(FeedItemInfo* pFeedItemInfo);
|
||||
|
||||
public:
|
||||
Rule();
|
||||
~Rule();
|
||||
void Compile(char* szRule);
|
||||
bool IsValid() { return m_bIsValid; }
|
||||
ERuleCommand GetCommand() { return m_eCommand; }
|
||||
const char* GetCategory() { return m_szCategory; }
|
||||
int GetPriority() { return m_iPriority; }
|
||||
int GetAddPriority() { return m_iAddPriority; }
|
||||
bool GetPause() { return m_bPause; }
|
||||
const char* GetDupeKey() { return m_szDupeKey; }
|
||||
const char* GetAddDupeKey() { return m_szAddDupeKey; }
|
||||
int GetDupeScore() { return m_iDupeScore; }
|
||||
int GetAddDupeScore() { return m_iAddDupeScore; }
|
||||
EDupeMode GetDupeMode() { return m_eDupeMode; }
|
||||
const char* GetRageId() { return m_szRageId; }
|
||||
const char* GetSeries() { return m_szSeries; }
|
||||
bool HasCategory() { return m_bHasCategory; }
|
||||
bool HasPriority() { return m_bHasPriority; }
|
||||
bool HasAddPriority() { return m_bHasAddPriority; }
|
||||
bool HasPause() { return m_bHasPause; }
|
||||
bool HasDupeScore() { return m_bHasDupeScore; }
|
||||
bool HasAddDupeScore() { return m_bHasAddDupeScore; }
|
||||
bool HasDupeKey() { return m_bHasDupeKey; }
|
||||
bool HasAddDupeKey() { return m_bHasAddDupeKey; }
|
||||
bool HasDupeMode() { return m_bHasDupeMode; }
|
||||
bool HasRageId() { return m_bHasRageId; }
|
||||
bool HasSeries() { return m_bHasSeries; }
|
||||
bool Match(FeedItemInfo* pFeedItemInfo);
|
||||
void ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr);
|
||||
const char* GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName);
|
||||
Rule() {}
|
||||
Rule(Rule&&) = delete; // catch performance issues
|
||||
void Compile(char* rule);
|
||||
bool IsValid() { return m_isValid; }
|
||||
ERuleCommand GetCommand() { return m_command; }
|
||||
const char* GetCategory() { return m_category; }
|
||||
int GetPriority() { return m_priority; }
|
||||
int GetAddPriority() { return m_addPriority; }
|
||||
bool GetPause() { return m_pause; }
|
||||
const char* GetDupeKey() { return m_dupeKey; }
|
||||
const char* GetAddDupeKey() { return m_addDupeKey; }
|
||||
int GetDupeScore() { return m_dupeScore; }
|
||||
int GetAddDupeScore() { return m_addDupeScore; }
|
||||
EDupeMode GetDupeMode() { return m_dupeMode; }
|
||||
const char* GetRageId() { return m_rageId; }
|
||||
const char* GetTvdbId() { return m_tvdbId; }
|
||||
const char* GetTvmazeId() { return m_tvmazeId; }
|
||||
const char* GetSeries() { return m_series; }
|
||||
bool HasCategory() { return m_hasCategory; }
|
||||
bool HasPriority() { return m_hasPriority; }
|
||||
bool HasAddPriority() { return m_hasAddPriority; }
|
||||
bool HasPause() { return m_hasPause; }
|
||||
bool HasDupeScore() { return m_hasDupeScore; }
|
||||
bool HasAddDupeScore() { return m_hasAddDupeScore; }
|
||||
bool HasDupeKey() { return m_hasDupeKey; }
|
||||
bool HasAddDupeKey() { return m_hasAddDupeKey; }
|
||||
bool HasDupeMode() { return m_hasDupeMode; }
|
||||
bool HasRageId() { return m_hasRageId; }
|
||||
bool HasTvdbId() { return m_hasTvdbId; }
|
||||
bool HasTvmazeId() { return m_hasTvmazeId; }
|
||||
bool HasSeries() { return m_hasSeries; }
|
||||
bool Match(FeedItemInfo& feedItemInfo);
|
||||
void ExpandRefValues(FeedItemInfo& feedItemInfo, CString* destStr, const char* patStr);
|
||||
const char* GetRefValue(FeedItemInfo& feedItemInfo, const char* varName);
|
||||
|
||||
private:
|
||||
bool m_isValid = false;
|
||||
ERuleCommand m_command = frAccept;
|
||||
CString m_category;
|
||||
int m_priority = 0;
|
||||
int m_addPriority = 0;
|
||||
bool m_pause = false;
|
||||
int m_dupeScore;
|
||||
int m_addDupeScore = 0;
|
||||
CString m_dupeKey;
|
||||
CString m_addDupeKey;
|
||||
EDupeMode m_dupeMode = dmScore;
|
||||
CString m_series;
|
||||
CString m_rageId;
|
||||
CString m_tvdbId;
|
||||
CString m_tvmazeId;
|
||||
bool m_hasCategory = false;
|
||||
bool m_hasPriority = false;
|
||||
bool m_hasAddPriority = false;
|
||||
bool m_hasPause = false;
|
||||
bool m_hasDupeScore = false;
|
||||
bool m_hasAddDupeScore = false;
|
||||
bool m_hasDupeKey = false;
|
||||
bool m_hasAddDupeKey = false;
|
||||
bool m_hasDupeMode = false;
|
||||
bool m_hasPatCategory = false;
|
||||
bool m_hasPatDupeKey = false;
|
||||
bool m_hasPatAddDupeKey = false;
|
||||
bool m_hasSeries = false;
|
||||
bool m_hasRageId = false;
|
||||
bool m_hasTvdbId = false;
|
||||
bool m_hasTvmazeId = false;
|
||||
CString m_patCategory;
|
||||
CString m_patDupeKey;
|
||||
CString m_patAddDupeKey;
|
||||
TermList m_terms;
|
||||
RefValues m_refValues;
|
||||
|
||||
char* CompileCommand(char* rule);
|
||||
char* CompileOptions(char* rule);
|
||||
bool CompileTerm(char* term);
|
||||
bool MatchExpression(FeedItemInfo& feedItemInfo);
|
||||
};
|
||||
|
||||
typedef std::deque<Rule*> RuleList;
|
||||
typedef std::deque<Rule> RuleList;
|
||||
|
||||
private:
|
||||
RuleList m_Rules;
|
||||
RuleList m_rules;
|
||||
|
||||
void Compile(const char* szFilter);
|
||||
void CompileRule(char* szRule);
|
||||
void ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo);
|
||||
|
||||
public:
|
||||
FeedFilter(const char* szFilter);
|
||||
~FeedFilter();
|
||||
void Match(FeedItemInfo* pFeedItemInfo);
|
||||
void Compile(const char* filter);
|
||||
void CompileRule(char* rule);
|
||||
void ApplyOptions(Rule& rule, FeedItemInfo& feedItemInfo);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,462 +14,189 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision: 0 $
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "FeedInfo.h"
|
||||
#include "DupeCoordinator.h"
|
||||
#include "Util.h"
|
||||
|
||||
extern DupeCoordinator* g_pDupeCoordinator;
|
||||
|
||||
FeedInfo::FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval,
|
||||
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority)
|
||||
FeedInfo::FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
|
||||
const char* filter, bool pauseNzb, const char* category, int priority, const char* extensions) :
|
||||
m_backlog(backlog), m_interval(interval), m_pauseNzb(pauseNzb), m_priority(priority)
|
||||
{
|
||||
m_iID = iID;
|
||||
m_szName = strdup(szName ? szName : "");
|
||||
m_szUrl = strdup(szUrl ? szUrl : "");
|
||||
m_szFilter = strdup(szFilter ? szFilter : "");
|
||||
m_iFilterHash = Util::HashBJ96(m_szFilter, strlen(m_szFilter), 0);
|
||||
m_szCategory = strdup(szCategory ? szCategory : "");
|
||||
m_iInterval = iInterval;
|
||||
m_bPauseNzb = bPauseNzb;
|
||||
m_iPriority = iPriority;
|
||||
m_tLastUpdate = 0;
|
||||
m_bPreview = false;
|
||||
m_eStatus = fsUndefined;
|
||||
m_szOutputFilename = NULL;
|
||||
m_bFetch = false;
|
||||
m_bForce = false;
|
||||
}
|
||||
|
||||
FeedInfo::~FeedInfo()
|
||||
{
|
||||
free(m_szName);
|
||||
free(m_szUrl);
|
||||
free(m_szFilter);
|
||||
free(m_szCategory);
|
||||
free(m_szOutputFilename);
|
||||
}
|
||||
|
||||
void FeedInfo::SetOutputFilename(const char* szOutputFilename)
|
||||
{
|
||||
free(m_szOutputFilename);
|
||||
m_szOutputFilename = strdup(szOutputFilename);
|
||||
}
|
||||
|
||||
|
||||
FeedItemInfo::Attr::Attr(const char* szName, const char* szValue)
|
||||
{
|
||||
m_szName = strdup(szName ? szName : "");
|
||||
m_szValue = strdup(szValue ? szValue : "");
|
||||
}
|
||||
|
||||
FeedItemInfo::Attr::~Attr()
|
||||
{
|
||||
free(m_szName);
|
||||
free(m_szValue);
|
||||
}
|
||||
|
||||
|
||||
FeedItemInfo::Attributes::~Attributes()
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
m_id = id;
|
||||
m_name = name ? name : "";
|
||||
if (m_name.Length() == 0)
|
||||
{
|
||||
delete *it;
|
||||
m_name.Format("Feed%i", m_id);
|
||||
}
|
||||
m_url = url ? url : "";
|
||||
m_filter = filter ? filter : "";
|
||||
m_filterHash = Util::HashBJ96(m_filter, strlen(m_filter), 0);
|
||||
m_category = category ? category : "";
|
||||
m_extensions = extensions ? extensions : "";
|
||||
}
|
||||
|
||||
void FeedItemInfo::Attributes::Add(const char* szName, const char* szValue)
|
||||
{
|
||||
push_back(new Attr(szName, szValue));
|
||||
}
|
||||
|
||||
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* szName)
|
||||
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* name)
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
for (Attr& attr : this)
|
||||
{
|
||||
Attr* pAttr = *it;
|
||||
if (!strcasecmp(pAttr->GetName(), szName))
|
||||
if (!strcasecmp(attr.GetName(), name))
|
||||
{
|
||||
return pAttr;
|
||||
return &attr;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
FeedItemInfo::FeedItemInfo()
|
||||
void FeedItemInfo::SetSeason(const char* season)
|
||||
{
|
||||
m_pSharedFeedData = NULL;
|
||||
m_szTitle = NULL;
|
||||
m_szFilename = NULL;
|
||||
m_szUrl = NULL;
|
||||
m_szCategory = strdup("");
|
||||
m_lSize = 0;
|
||||
m_tTime = 0;
|
||||
m_iImdbId = 0;
|
||||
m_iRageId = 0;
|
||||
m_szDescription = strdup("");
|
||||
m_szSeason = NULL;
|
||||
m_szEpisode = NULL;
|
||||
m_iSeasonNum = 0;
|
||||
m_iEpisodeNum = 0;
|
||||
m_bSeasonEpisodeParsed = false;
|
||||
m_szAddCategory = strdup("");
|
||||
m_bPauseNzb = false;
|
||||
m_iPriority = 0;
|
||||
m_eStatus = isUnknown;
|
||||
m_eMatchStatus = msIgnored;
|
||||
m_iMatchRule = 0;
|
||||
m_szDupeKey = NULL;
|
||||
m_iDupeScore = 0;
|
||||
m_eDupeMode = dmScore;
|
||||
m_szDupeStatus = NULL;
|
||||
m_season = season;
|
||||
m_seasonNum = season ? ParsePrefixedInt(season) : 0;
|
||||
}
|
||||
|
||||
FeedItemInfo::~FeedItemInfo()
|
||||
void FeedItemInfo::SetEpisode(const char* episode)
|
||||
{
|
||||
free(m_szTitle);
|
||||
free(m_szFilename);
|
||||
free(m_szUrl);
|
||||
free(m_szCategory);
|
||||
free(m_szDescription);
|
||||
free(m_szSeason);
|
||||
free(m_szEpisode);
|
||||
free(m_szAddCategory);
|
||||
free(m_szDupeKey);
|
||||
free(m_szDupeStatus);
|
||||
m_episode = episode;
|
||||
m_episodeNum = episode ? ParsePrefixedInt(episode) : 0;
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetTitle(const char* szTitle)
|
||||
int FeedItemInfo::ParsePrefixedInt(const char *value)
|
||||
{
|
||||
free(m_szTitle);
|
||||
m_szTitle = szTitle ? strdup(szTitle) : NULL;
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetFilename(const char* szFilename)
|
||||
{
|
||||
free(m_szFilename);
|
||||
m_szFilename = szFilename ? strdup(szFilename) : NULL;
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetUrl(const char* szUrl)
|
||||
{
|
||||
free(m_szUrl);
|
||||
m_szUrl = szUrl ? strdup(szUrl) : NULL;
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetCategory(const char* szCategory)
|
||||
{
|
||||
free(m_szCategory);
|
||||
m_szCategory = strdup(szCategory ? szCategory: "");
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetDescription(const char* szDescription)
|
||||
{
|
||||
free(m_szDescription);
|
||||
m_szDescription = strdup(szDescription ? szDescription: "");
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetSeason(const char* szSeason)
|
||||
{
|
||||
free(m_szSeason);
|
||||
m_szSeason = szSeason ? strdup(szSeason) : NULL;
|
||||
m_iSeasonNum = szSeason ? ParsePrefixedInt(szSeason) : 0;
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetEpisode(const char* szEpisode)
|
||||
{
|
||||
free(m_szEpisode);
|
||||
m_szEpisode = szEpisode ? strdup(szEpisode) : NULL;
|
||||
m_iEpisodeNum = szEpisode ? ParsePrefixedInt(szEpisode) : 0;
|
||||
}
|
||||
|
||||
int FeedItemInfo::ParsePrefixedInt(const char *szValue)
|
||||
{
|
||||
const char* szVal = szValue;
|
||||
if (!strchr("0123456789", *szVal))
|
||||
const char* val = value;
|
||||
if (!strchr("0123456789", *val))
|
||||
{
|
||||
szVal++;
|
||||
val++;
|
||||
}
|
||||
return atoi(szVal);
|
||||
return atoi(val);
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetAddCategory(const char* szAddCategory)
|
||||
void FeedItemInfo::AppendDupeKey(const char* extraDupeKey)
|
||||
{
|
||||
free(m_szAddCategory);
|
||||
m_szAddCategory = strdup(szAddCategory ? szAddCategory : "");
|
||||
}
|
||||
|
||||
void FeedItemInfo::SetDupeKey(const char* szDupeKey)
|
||||
{
|
||||
free(m_szDupeKey);
|
||||
m_szDupeKey = strdup(szDupeKey ? szDupeKey : "");
|
||||
}
|
||||
|
||||
void FeedItemInfo::AppendDupeKey(const char* szExtraDupeKey)
|
||||
{
|
||||
if (!m_szDupeKey || *m_szDupeKey == '\0' || !szExtraDupeKey || *szExtraDupeKey == '\0')
|
||||
if (!m_dupeKey.Empty() && !Util::EmptyStr(extraDupeKey))
|
||||
{
|
||||
return;
|
||||
m_dupeKey.AppendFmt("-%s", extraDupeKey);
|
||||
}
|
||||
|
||||
int iLen = (m_szDupeKey ? strlen(m_szDupeKey) : 0) + 1 + strlen(szExtraDupeKey) + 1;
|
||||
char* szNewKey = (char*)malloc(iLen);
|
||||
snprintf(szNewKey, iLen, "%s-%s", m_szDupeKey, szExtraDupeKey);
|
||||
szNewKey[iLen - 1] = '\0';
|
||||
|
||||
free(m_szDupeKey);
|
||||
m_szDupeKey = szNewKey;
|
||||
}
|
||||
|
||||
void FeedItemInfo::BuildDupeKey(const char* szRageId, const char* szSeries)
|
||||
void FeedItemInfo::BuildDupeKey(const char* rageId, const char* tvdbId, const char* tvmazeId, const char* series)
|
||||
{
|
||||
int iRageId = szRageId && *szRageId ? atoi(szRageId) : m_iRageId;
|
||||
int rageIdVal = !Util::EmptyStr(rageId) ? atoi(rageId) : m_rageId;
|
||||
int tvdbIdVal = !Util::EmptyStr(tvdbId) ? atoi(tvdbId) : m_tvdbId;
|
||||
int tvmazeIdVal = !Util::EmptyStr(tvmazeId) ? atoi(tvmazeId) : m_tvmazeId;
|
||||
|
||||
free(m_szDupeKey);
|
||||
|
||||
if (m_iImdbId != 0)
|
||||
if (m_imdbId != 0)
|
||||
{
|
||||
m_szDupeKey = (char*)malloc(20);
|
||||
snprintf(m_szDupeKey, 20, "imdb=%i", m_iImdbId);
|
||||
m_dupeKey.Format("imdb=%i", m_imdbId);
|
||||
}
|
||||
else if (szSeries && *szSeries && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
else if (!Util::EmptyStr(series) && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
{
|
||||
int iLen = strlen(szSeries) + 50;
|
||||
m_szDupeKey = (char*)malloc(iLen);
|
||||
snprintf(m_szDupeKey, iLen, "series=%s-%s-%s", szSeries, m_szSeason, m_szEpisode);
|
||||
m_szDupeKey[iLen-1] = '\0';
|
||||
m_dupeKey.Format("series=%s-%s-%s", series, *m_season, *m_episode);
|
||||
}
|
||||
else if (iRageId != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
else if (rageIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
{
|
||||
m_szDupeKey = (char*)malloc(100);
|
||||
snprintf(m_szDupeKey, 100, "rageid=%i-%s-%s", iRageId, m_szSeason, m_szEpisode);
|
||||
m_szDupeKey[100-1] = '\0';
|
||||
m_dupeKey.Format("rageid=%i-%s-%s", rageIdVal, *m_season, *m_episode);
|
||||
}
|
||||
else if (tvdbIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
{
|
||||
m_dupeKey.Format("tvdbid=%i-%s-%s", tvdbIdVal, *m_season, *m_episode);
|
||||
}
|
||||
else if (tvmazeIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
|
||||
{
|
||||
m_dupeKey.Format("tvmazeid=%i-%s-%s", tvmazeIdVal, *m_season, *m_episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_szDupeKey = strdup("");
|
||||
m_dupeKey = "";
|
||||
}
|
||||
}
|
||||
|
||||
int FeedItemInfo::GetSeasonNum()
|
||||
{
|
||||
if (!m_szSeason && !m_bSeasonEpisodeParsed)
|
||||
if (!m_season && !m_seasonEpisodeParsed)
|
||||
{
|
||||
ParseSeasonEpisode();
|
||||
}
|
||||
|
||||
return m_iSeasonNum;
|
||||
return m_seasonNum;
|
||||
}
|
||||
|
||||
int FeedItemInfo::GetEpisodeNum()
|
||||
{
|
||||
if (!m_szEpisode && !m_bSeasonEpisodeParsed)
|
||||
if (!m_episode && !m_seasonEpisodeParsed)
|
||||
{
|
||||
ParseSeasonEpisode();
|
||||
}
|
||||
|
||||
return m_iEpisodeNum;
|
||||
return m_episodeNum;
|
||||
}
|
||||
|
||||
void FeedItemInfo::ParseSeasonEpisode()
|
||||
{
|
||||
m_bSeasonEpisodeParsed = true;
|
||||
m_seasonEpisodeParsed = true;
|
||||
|
||||
RegEx* pRegEx = m_pSharedFeedData->GetSeasonEpisodeRegEx();
|
||||
const char* pattern = "[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]";
|
||||
|
||||
if (pRegEx->Match(m_szTitle))
|
||||
std::unique_ptr<RegEx>& regEx = m_feedFilterHelper->GetRegEx(1);
|
||||
if (!regEx)
|
||||
{
|
||||
char szRegValue[100];
|
||||
char szValue[100];
|
||||
regEx = std::make_unique<RegEx>(pattern, 10);
|
||||
}
|
||||
|
||||
snprintf(szValue, 100, "S%02d", atoi(m_szTitle + pRegEx->GetMatchStart(1)));
|
||||
szValue[100-1] = '\0';
|
||||
SetSeason(szValue);
|
||||
if (regEx->Match(m_title))
|
||||
{
|
||||
SetSeason(BString<100>("S%02d", atoi(m_title + regEx->GetMatchStart(1))));
|
||||
|
||||
int iLen = pRegEx->GetMatchLen(2);
|
||||
iLen = iLen < 99 ? iLen : 99;
|
||||
strncpy(szRegValue, m_szTitle + pRegEx->GetMatchStart(2), pRegEx->GetMatchLen(2));
|
||||
szRegValue[iLen] = '\0';
|
||||
snprintf(szValue, 100, "E%s", szRegValue);
|
||||
szValue[100-1] = '\0';
|
||||
Util::ReduceStr(szValue, "-", "");
|
||||
for (char* p = szValue; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02
|
||||
SetEpisode(szValue);
|
||||
BString<100> regValue;
|
||||
regValue.Set(m_title + regEx->GetMatchStart(2), regEx->GetMatchLen(2));
|
||||
|
||||
BString<100> episode("E%s", *regValue);
|
||||
Util::ReduceStr(episode, "-", "");
|
||||
for (char* p = episode; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02
|
||||
SetEpisode(episode);
|
||||
}
|
||||
}
|
||||
|
||||
const char* FeedItemInfo::GetDupeStatus()
|
||||
{
|
||||
if (!m_szDupeStatus)
|
||||
if (!m_dupeStatus)
|
||||
{
|
||||
const char* szDupeStatusName[] = { "", "QUEUED", "DOWNLOADING", "3", "SUCCESS", "5", "6", "7", "WARNING",
|
||||
"9", "10", "11", "12", "13", "14", "15", "FAILURE" };
|
||||
char szStatuses[200];
|
||||
szStatuses[0] = '\0';
|
||||
|
||||
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
|
||||
DupeCoordinator::EDupeStatus eDupeStatus = g_pDupeCoordinator->GetDupeStatus(pDownloadQueue, m_szTitle, m_szDupeKey);
|
||||
DownloadQueue::Unlock();
|
||||
|
||||
for (int i = 1; i <= (int)DupeCoordinator::dsFailure; i = i << 1)
|
||||
{
|
||||
if (eDupeStatus & i)
|
||||
{
|
||||
if (*szStatuses)
|
||||
{
|
||||
strcat(szStatuses, ",");
|
||||
}
|
||||
strcat(szStatuses, szDupeStatusName[i]);
|
||||
}
|
||||
}
|
||||
|
||||
m_szDupeStatus = strdup(szStatuses);
|
||||
BString<1024> statuses;
|
||||
m_feedFilterHelper->CalcDupeStatus(m_title, m_dupeKey, statuses, statuses.Capacity());
|
||||
m_dupeStatus = statuses;
|
||||
}
|
||||
|
||||
return m_szDupeStatus;
|
||||
return m_dupeStatus;
|
||||
}
|
||||
|
||||
|
||||
FeedHistoryInfo::FeedHistoryInfo(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
|
||||
{
|
||||
m_szUrl = szUrl ? strdup(szUrl) : NULL;
|
||||
m_eStatus = eStatus;
|
||||
m_tLastSeen = tLastSeen;
|
||||
}
|
||||
|
||||
FeedHistoryInfo::~FeedHistoryInfo()
|
||||
{
|
||||
free(m_szUrl);
|
||||
}
|
||||
|
||||
|
||||
FeedHistory::~FeedHistory()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void FeedHistory::Clear()
|
||||
void FeedHistory::Remove(const char* url)
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
void FeedHistory::Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
|
||||
{
|
||||
push_back(new FeedHistoryInfo(szUrl, eStatus, tLastSeen));
|
||||
}
|
||||
|
||||
void FeedHistory::Remove(const char* szUrl)
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
{
|
||||
FeedHistoryInfo* pFeedHistoryInfo = *it;
|
||||
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
|
||||
FeedHistoryInfo& feedHistoryInfo = *it;
|
||||
if (!strcmp(feedHistoryInfo.GetUrl(), url))
|
||||
{
|
||||
delete pFeedHistoryInfo;
|
||||
erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FeedHistoryInfo* FeedHistory::Find(const char* szUrl)
|
||||
FeedHistoryInfo* FeedHistory::Find(const char* url)
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
for (FeedHistoryInfo& feedHistoryInfo : this)
|
||||
{
|
||||
FeedHistoryInfo* pFeedHistoryInfo = *it;
|
||||
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
|
||||
if (!strcmp(feedHistoryInfo.GetUrl(), url))
|
||||
{
|
||||
return pFeedHistoryInfo;
|
||||
return &feedHistoryInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
FeedItemInfos::FeedItemInfos()
|
||||
{
|
||||
debug("Creating FeedItemInfos");
|
||||
|
||||
m_iRefCount = 0;
|
||||
}
|
||||
|
||||
FeedItemInfos::~FeedItemInfos()
|
||||
{
|
||||
debug("Destroing FeedItemInfos");
|
||||
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedItemInfos::Retain()
|
||||
{
|
||||
m_iRefCount++;
|
||||
}
|
||||
|
||||
void FeedItemInfos::Release()
|
||||
{
|
||||
m_iRefCount--;
|
||||
if (m_iRefCount <= 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedItemInfos::Add(FeedItemInfo* pFeedItemInfo)
|
||||
{
|
||||
push_back(pFeedItemInfo);
|
||||
pFeedItemInfo->SetSharedFeedData(&m_SharedFeedData);
|
||||
}
|
||||
|
||||
|
||||
SharedFeedData::SharedFeedData()
|
||||
{
|
||||
m_pSeasonEpisodeRegEx = NULL;
|
||||
}
|
||||
|
||||
SharedFeedData::~SharedFeedData()
|
||||
{
|
||||
delete m_pSeasonEpisodeRegEx;
|
||||
}
|
||||
|
||||
RegEx* SharedFeedData::GetSeasonEpisodeRegEx()
|
||||
{
|
||||
if (!m_pSeasonEpisodeRegEx)
|
||||
{
|
||||
m_pSeasonEpisodeRegEx = new RegEx("[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]", 10);
|
||||
}
|
||||
|
||||
return m_pSeasonEpisodeRegEx;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,25 +14,17 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision: 0 $
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FEEDINFO_H
|
||||
#define FEEDINFO_H
|
||||
|
||||
#include <deque>
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Util.h"
|
||||
#include "DownloadInfo.h"
|
||||
|
||||
|
||||
class FeedInfo
|
||||
{
|
||||
public:
|
||||
@@ -44,61 +36,67 @@ public:
|
||||
fsFailed
|
||||
};
|
||||
|
||||
private:
|
||||
int m_iID;
|
||||
char* m_szName;
|
||||
char* m_szUrl;
|
||||
int m_iInterval;
|
||||
char* m_szFilter;
|
||||
unsigned int m_iFilterHash;
|
||||
bool m_bPauseNzb;
|
||||
char* m_szCategory;
|
||||
int m_iPriority;
|
||||
time_t m_tLastUpdate;
|
||||
bool m_bPreview;
|
||||
EStatus m_eStatus;
|
||||
char* m_szOutputFilename;
|
||||
bool m_bFetch;
|
||||
bool m_bForce;
|
||||
FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
|
||||
const char* filter, bool pauseNzb, const char* category, int priority,
|
||||
const char* extensions);
|
||||
int GetId() { return m_id; }
|
||||
const char* GetName() { return m_name; }
|
||||
const char* GetUrl() { return m_url; }
|
||||
int GetInterval() { return m_interval; }
|
||||
const char* GetFilter() { return m_filter; }
|
||||
uint32 GetFilterHash() { return m_filterHash; }
|
||||
bool GetPauseNzb() { return m_pauseNzb; }
|
||||
const char* GetCategory() { return m_category; }
|
||||
int GetPriority() { return m_priority; }
|
||||
const char* GetExtensions() { return m_extensions; }
|
||||
time_t GetLastUpdate() { return m_lastUpdate; }
|
||||
void SetLastUpdate(time_t lastUpdate) { m_lastUpdate = lastUpdate; }
|
||||
time_t GetNextUpdate() { return m_nextUpdate; }
|
||||
void SetNextUpdate(time_t nextUpdate) { m_nextUpdate = nextUpdate; }
|
||||
int GetLastInterval() { return m_lastInterval; }
|
||||
void SetLastInterval(int lastInterval) { m_lastInterval = lastInterval; }
|
||||
bool GetPreview() { return m_preview; }
|
||||
void SetPreview(bool preview) { m_preview = preview; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void SetStatus(EStatus Status) { m_status = Status; }
|
||||
const char* GetOutputFilename() { return m_outputFilename; }
|
||||
void SetOutputFilename(const char* outputFilename) { m_outputFilename = outputFilename; }
|
||||
bool GetFetch() { return m_fetch; }
|
||||
void SetFetch(bool fetch) { m_fetch = fetch; }
|
||||
bool GetForce() { return m_force; }
|
||||
void SetForce(bool force) { m_force = force; }
|
||||
bool GetBacklog() { return m_backlog; }
|
||||
void SetBacklog(bool backlog) { m_backlog = backlog; }
|
||||
|
||||
public:
|
||||
FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval,
|
||||
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority);
|
||||
~FeedInfo();
|
||||
int GetID() { return m_iID; }
|
||||
const char* GetName() { return m_szName; }
|
||||
const char* GetUrl() { return m_szUrl; }
|
||||
int GetInterval() { return m_iInterval; }
|
||||
const char* GetFilter() { return m_szFilter; }
|
||||
unsigned int GetFilterHash() { return m_iFilterHash; }
|
||||
bool GetPauseNzb() { return m_bPauseNzb; }
|
||||
const char* GetCategory() { return m_szCategory; }
|
||||
int GetPriority() { return m_iPriority; }
|
||||
time_t GetLastUpdate() { return m_tLastUpdate; }
|
||||
void SetLastUpdate(time_t tLastUpdate) { m_tLastUpdate = tLastUpdate; }
|
||||
bool GetPreview() { return m_bPreview; }
|
||||
void SetPreview(bool bPreview) { m_bPreview = bPreview; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void SetStatus(EStatus Status) { m_eStatus = Status; }
|
||||
const char* GetOutputFilename() { return m_szOutputFilename; }
|
||||
void SetOutputFilename(const char* szOutputFilename);
|
||||
bool GetFetch() { return m_bFetch; }
|
||||
void SetFetch(bool bFetch) { m_bFetch = bFetch; }
|
||||
bool GetForce() { return m_bForce; }
|
||||
void SetForce(bool bForce) { m_bForce = bForce; }
|
||||
private:
|
||||
int m_id;
|
||||
CString m_name;
|
||||
CString m_url;
|
||||
bool m_backlog;
|
||||
int m_interval;
|
||||
CString m_filter;
|
||||
uint32 m_filterHash;
|
||||
bool m_pauseNzb;
|
||||
CString m_category;
|
||||
CString m_extensions;
|
||||
int m_priority;
|
||||
time_t m_lastUpdate = 0;
|
||||
time_t m_nextUpdate = 0;
|
||||
int m_lastInterval = 0;
|
||||
bool m_preview = false;
|
||||
EStatus m_status = fsUndefined;
|
||||
CString m_outputFilename;
|
||||
bool m_fetch = false;
|
||||
bool m_force = false;
|
||||
};
|
||||
|
||||
typedef std::deque<FeedInfo*> Feeds;
|
||||
typedef std::deque<std::unique_ptr<FeedInfo>> Feeds;
|
||||
|
||||
class SharedFeedData
|
||||
class FeedFilterHelper
|
||||
{
|
||||
private:
|
||||
RegEx* m_pSeasonEpisodeRegEx;
|
||||
|
||||
public:
|
||||
SharedFeedData();
|
||||
~SharedFeedData();
|
||||
RegEx* GetSeasonEpisodeRegEx();
|
||||
virtual std::unique_ptr<RegEx>& GetRegEx(int id) = 0;
|
||||
virtual void CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen) = 0;
|
||||
};
|
||||
|
||||
class FeedItemInfo
|
||||
@@ -121,124 +119,113 @@ public:
|
||||
|
||||
class Attr
|
||||
{
|
||||
private:
|
||||
char* m_szName;
|
||||
char* m_szValue;
|
||||
public:
|
||||
Attr(const char* szName, const char* szValue);
|
||||
~Attr();
|
||||
const char* GetName() { return m_szName; }
|
||||
const char* GetValue() { return m_szValue; }
|
||||
Attr(const char* name, const char* value) :
|
||||
m_name(name ? name : ""), m_value(value ? value : "") {}
|
||||
const char* GetName() { return m_name; }
|
||||
const char* GetValue() { return m_value; }
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_value;
|
||||
};
|
||||
|
||||
typedef std::deque<Attr*> AttributesBase;
|
||||
|
||||
typedef std::deque<Attr> AttributesBase;
|
||||
|
||||
class Attributes: public AttributesBase
|
||||
{
|
||||
public:
|
||||
~Attributes();
|
||||
void Add(const char* szName, const char* szValue);
|
||||
Attr* Find(const char* szName);
|
||||
Attr* Find(const char* name);
|
||||
};
|
||||
|
||||
FeedItemInfo() {}
|
||||
FeedItemInfo(FeedItemInfo&&) = delete; // catch performance issues
|
||||
void SetFeedFilterHelper(FeedFilterHelper* feedFilterHelper) { m_feedFilterHelper = feedFilterHelper; }
|
||||
const char* GetTitle() { return m_title; }
|
||||
void SetTitle(const char* title) { m_title = title; }
|
||||
const char* GetFilename() { return m_filename; }
|
||||
void SetFilename(const char* filename) { m_filename = filename; }
|
||||
const char* GetUrl() { return m_url; }
|
||||
void SetUrl(const char* url) { m_url = url; }
|
||||
int64 GetSize() { return m_size; }
|
||||
void SetSize(int64 size) { m_size = size; }
|
||||
const char* GetCategory() { return m_category; }
|
||||
void SetCategory(const char* category) { m_category = category; }
|
||||
int GetImdbId() { return m_imdbId; }
|
||||
void SetImdbId(int imdbId) { m_imdbId = imdbId; }
|
||||
int GetRageId() { return m_rageId; }
|
||||
void SetRageId(int rageId) { m_rageId = rageId; }
|
||||
int GetTvdbId() { return m_tvdbId; }
|
||||
void SetTvdbId(int tvdbId) { m_tvdbId = tvdbId; }
|
||||
int GetTvmazeId() { return m_tvmazeId; }
|
||||
void SetTvmazeId(int tvmazeId) { m_tvmazeId = tvmazeId; }
|
||||
const char* GetDescription() { return m_description; }
|
||||
void SetDescription(const char* description) { m_description = description ? description: ""; }
|
||||
const char* GetSeason() { return m_season; }
|
||||
void SetSeason(const char* season);
|
||||
const char* GetEpisode() { return m_episode; }
|
||||
void SetEpisode(const char* episode);
|
||||
int GetSeasonNum();
|
||||
int GetEpisodeNum();
|
||||
const char* GetAddCategory() { return m_addCategory; }
|
||||
void SetAddCategory(const char* addCategory) { m_addCategory = addCategory ? addCategory : ""; }
|
||||
bool GetPauseNzb() { return m_pauseNzb; }
|
||||
void SetPauseNzb(bool pauseNzb) { m_pauseNzb = pauseNzb; }
|
||||
int GetPriority() { return m_priority; }
|
||||
void SetPriority(int priority) { m_priority = priority; }
|
||||
time_t GetTime() { return m_time; }
|
||||
void SetTime(time_t time) { m_time = time; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void SetStatus(EStatus status) { m_status = status; }
|
||||
EMatchStatus GetMatchStatus() { return m_matchStatus; }
|
||||
void SetMatchStatus(EMatchStatus matchStatus) { m_matchStatus = matchStatus; }
|
||||
int GetMatchRule() { return m_matchRule; }
|
||||
void SetMatchRule(int matchRule) { m_matchRule = matchRule; }
|
||||
const char* GetDupeKey() { return m_dupeKey; }
|
||||
void SetDupeKey(const char* dupeKey) { m_dupeKey = dupeKey ? dupeKey : ""; }
|
||||
void AppendDupeKey(const char* extraDupeKey);
|
||||
void BuildDupeKey(const char* rageId, const char* tvdbId, const char* tvmazeId, const char* series);
|
||||
int GetDupeScore() { return m_dupeScore; }
|
||||
void SetDupeScore(int dupeScore) { m_dupeScore = dupeScore; }
|
||||
EDupeMode GetDupeMode() { return m_dupeMode; }
|
||||
void SetDupeMode(EDupeMode dupeMode) { m_dupeMode = dupeMode; }
|
||||
const char* GetDupeStatus();
|
||||
Attributes* GetAttributes() { return &m_attributes; }
|
||||
|
||||
private:
|
||||
char* m_szTitle;
|
||||
char* m_szFilename;
|
||||
char* m_szUrl;
|
||||
time_t m_tTime;
|
||||
long long m_lSize;
|
||||
char* m_szCategory;
|
||||
int m_iImdbId;
|
||||
int m_iRageId;
|
||||
char* m_szDescription;
|
||||
char* m_szSeason;
|
||||
char* m_szEpisode;
|
||||
int m_iSeasonNum;
|
||||
int m_iEpisodeNum;
|
||||
bool m_bSeasonEpisodeParsed;
|
||||
char* m_szAddCategory;
|
||||
bool m_bPauseNzb;
|
||||
int m_iPriority;
|
||||
EStatus m_eStatus;
|
||||
EMatchStatus m_eMatchStatus;
|
||||
int m_iMatchRule;
|
||||
char* m_szDupeKey;
|
||||
int m_iDupeScore;
|
||||
EDupeMode m_eDupeMode;
|
||||
char* m_szDupeStatus;
|
||||
SharedFeedData* m_pSharedFeedData;
|
||||
Attributes m_Attributes;
|
||||
CString m_title;
|
||||
CString m_filename;
|
||||
CString m_url;
|
||||
time_t m_time = 0;
|
||||
int64 m_size = 0;
|
||||
CString m_category = "";
|
||||
int m_imdbId = 0;
|
||||
int m_rageId = 0;
|
||||
int m_tvdbId = 0;
|
||||
int m_tvmazeId = 0;
|
||||
CString m_description = "";
|
||||
CString m_season;
|
||||
CString m_episode;
|
||||
int m_seasonNum = 0;
|
||||
int m_episodeNum = 0;
|
||||
bool m_seasonEpisodeParsed = false;
|
||||
CString m_addCategory = "";
|
||||
bool m_pauseNzb = false;
|
||||
int m_priority = 0;
|
||||
EStatus m_status = isUnknown;
|
||||
EMatchStatus m_matchStatus = msIgnored;
|
||||
int m_matchRule = 0;
|
||||
CString m_dupeKey;
|
||||
int m_dupeScore = 0;
|
||||
EDupeMode m_dupeMode = dmScore;
|
||||
CString m_dupeStatus;
|
||||
FeedFilterHelper* m_feedFilterHelper = nullptr;
|
||||
Attributes m_attributes;
|
||||
|
||||
int ParsePrefixedInt(const char *szValue);
|
||||
void ParseSeasonEpisode();
|
||||
|
||||
public:
|
||||
FeedItemInfo();
|
||||
~FeedItemInfo();
|
||||
void SetSharedFeedData(SharedFeedData* pSharedFeedData) { m_pSharedFeedData = pSharedFeedData; }
|
||||
const char* GetTitle() { return m_szTitle; }
|
||||
void SetTitle(const char* szTitle);
|
||||
const char* GetFilename() { return m_szFilename; }
|
||||
void SetFilename(const char* szFilename);
|
||||
const char* GetUrl() { return m_szUrl; }
|
||||
void SetUrl(const char* szUrl);
|
||||
long long GetSize() { return m_lSize; }
|
||||
void SetSize(long long lSize) { m_lSize = lSize; }
|
||||
const char* GetCategory() { return m_szCategory; }
|
||||
void SetCategory(const char* szCategory);
|
||||
int GetImdbId() { return m_iImdbId; }
|
||||
void SetImdbId(int iImdbId) { m_iImdbId = iImdbId; }
|
||||
int GetRageId() { return m_iRageId; }
|
||||
void SetRageId(int iRageId) { m_iRageId = iRageId; }
|
||||
const char* GetDescription() { return m_szDescription; }
|
||||
void SetDescription(const char* szDescription);
|
||||
const char* GetSeason() { return m_szSeason; }
|
||||
void SetSeason(const char* szSeason);
|
||||
const char* GetEpisode() { return m_szEpisode; }
|
||||
void SetEpisode(const char* szEpisode);
|
||||
int GetSeasonNum();
|
||||
int GetEpisodeNum();
|
||||
const char* GetAddCategory() { return m_szAddCategory; }
|
||||
void SetAddCategory(const char* szAddCategory);
|
||||
bool GetPauseNzb() { return m_bPauseNzb; }
|
||||
void SetPauseNzb(bool bPauseNzb) { m_bPauseNzb = bPauseNzb; }
|
||||
int GetPriority() { return m_iPriority; }
|
||||
void SetPriority(int iPriority) { m_iPriority = iPriority; }
|
||||
time_t GetTime() { return m_tTime; }
|
||||
void SetTime(time_t tTime) { m_tTime = tTime; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
|
||||
EMatchStatus GetMatchStatus() { return m_eMatchStatus; }
|
||||
void SetMatchStatus(EMatchStatus eMatchStatus) { m_eMatchStatus = eMatchStatus; }
|
||||
int GetMatchRule() { return m_iMatchRule; }
|
||||
void SetMatchRule(int iMatchRule) { m_iMatchRule = iMatchRule; }
|
||||
const char* GetDupeKey() { return m_szDupeKey; }
|
||||
void SetDupeKey(const char* szDupeKey);
|
||||
void AppendDupeKey(const char* szExtraDupeKey);
|
||||
void BuildDupeKey(const char* szRageId, const char* szSeries);
|
||||
int GetDupeScore() { return m_iDupeScore; }
|
||||
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
|
||||
EDupeMode GetDupeMode() { return m_eDupeMode; }
|
||||
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
|
||||
const char* GetDupeStatus();
|
||||
Attributes* GetAttributes() { return &m_Attributes; }
|
||||
int ParsePrefixedInt(const char *value);
|
||||
void ParseSeasonEpisode();
|
||||
};
|
||||
|
||||
typedef std::deque<FeedItemInfo*> FeedItemInfosBase;
|
||||
|
||||
class FeedItemInfos : public FeedItemInfosBase
|
||||
{
|
||||
private:
|
||||
int m_iRefCount;
|
||||
SharedFeedData m_SharedFeedData;
|
||||
|
||||
public:
|
||||
FeedItemInfos();
|
||||
~FeedItemInfos();
|
||||
void Retain();
|
||||
void Release();
|
||||
void Add(FeedItemInfo* pFeedItemInfo);
|
||||
};
|
||||
typedef std::deque<FeedItemInfo> FeedItemList;
|
||||
|
||||
class FeedHistoryInfo
|
||||
{
|
||||
@@ -250,31 +237,27 @@ public:
|
||||
hsFetched
|
||||
};
|
||||
|
||||
private:
|
||||
char* m_szUrl;
|
||||
EStatus m_eStatus;
|
||||
time_t m_tLastSeen;
|
||||
FeedHistoryInfo(const char* url, EStatus status, time_t lastSeen) :
|
||||
m_url(url), m_status(status), m_lastSeen(lastSeen) {}
|
||||
const char* GetUrl() { return m_url; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void SetStatus(EStatus Status) { m_status = Status; }
|
||||
time_t GetLastSeen() { return m_lastSeen; }
|
||||
void SetLastSeen(time_t lastSeen) { m_lastSeen = lastSeen; }
|
||||
|
||||
public:
|
||||
FeedHistoryInfo(const char* szUrl, EStatus eStatus, time_t tLastSeen);
|
||||
~FeedHistoryInfo();
|
||||
const char* GetUrl() { return m_szUrl; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void SetStatus(EStatus Status) { m_eStatus = Status; }
|
||||
time_t GetLastSeen() { return m_tLastSeen; }
|
||||
void SetLastSeen(time_t tLastSeen) { m_tLastSeen = tLastSeen; }
|
||||
private:
|
||||
CString m_url;
|
||||
EStatus m_status;
|
||||
time_t m_lastSeen;
|
||||
};
|
||||
|
||||
typedef std::deque<FeedHistoryInfo*> FeedHistoryBase;
|
||||
typedef std::deque<FeedHistoryInfo> FeedHistoryBase;
|
||||
|
||||
class FeedHistory : public FeedHistoryBase
|
||||
{
|
||||
public:
|
||||
~FeedHistory();
|
||||
void Clear();
|
||||
void Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen);
|
||||
void Remove(const char* szUrl);
|
||||
FeedHistoryInfo* Find(const char* szUrl);
|
||||
void Remove(const char* url);
|
||||
FeedHistoryInfo* Find(const char* url);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file if part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,147 +15,114 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "ColoredFrontend.h"
|
||||
#include "Util.h"
|
||||
|
||||
ColoredFrontend::ColoredFrontend()
|
||||
{
|
||||
m_bSummary = true;
|
||||
m_bNeedGoBack = false;
|
||||
m_summary = true;
|
||||
#ifdef WIN32
|
||||
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
m_console = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ColoredFrontend::BeforePrint()
|
||||
{
|
||||
if (m_bNeedGoBack)
|
||||
if (m_needGoBack)
|
||||
{
|
||||
// go back one line
|
||||
#ifdef WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
|
||||
GetConsoleScreenBufferInfo(m_hConsole, &BufInfo);
|
||||
GetConsoleScreenBufferInfo(m_console, &BufInfo);
|
||||
BufInfo.dwCursorPosition.Y--;
|
||||
SetConsoleCursorPosition(m_hConsole, BufInfo.dwCursorPosition);
|
||||
SetConsoleCursorPosition(m_console, BufInfo.dwCursorPosition);
|
||||
#else
|
||||
printf("\r\033[1A");
|
||||
#endif
|
||||
m_bNeedGoBack = false;
|
||||
m_needGoBack = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ColoredFrontend::PrintStatus()
|
||||
{
|
||||
char tmp[1024];
|
||||
char timeString[100];
|
||||
timeString[0] = '\0';
|
||||
int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed;
|
||||
BString<100> timeString;
|
||||
int currentDownloadSpeed = m_standBy ? 0 : m_currentDownloadSpeed;
|
||||
|
||||
if (iCurrentDownloadSpeed > 0 && !m_bPauseDownload)
|
||||
if (currentDownloadSpeed > 0 && !m_pauseDownload)
|
||||
{
|
||||
long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed);
|
||||
int64 remain_sec = (int64)(m_remainingSize / currentDownloadSpeed);
|
||||
int h = (int)(remain_sec / 3600);
|
||||
int m = (int)((remain_sec % 3600) / 60);
|
||||
int s = (int)(remain_sec % 60);
|
||||
sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s);
|
||||
timeString.Format(" (~ %.2d:%.2d:%.2d)", h, m, s);
|
||||
}
|
||||
|
||||
char szDownloadLimit[128];
|
||||
if (m_iDownloadLimit > 0)
|
||||
BString<100> downloadLimit;
|
||||
if (m_downloadLimit > 0)
|
||||
{
|
||||
sprintf(szDownloadLimit, ", Limit %.0f KB/s", (float)m_iDownloadLimit / 1024.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
szDownloadLimit[0] = 0;
|
||||
downloadLimit.Format(", Limit %i KB/s", m_downloadLimit / 1024);
|
||||
}
|
||||
|
||||
char szPostStatus[128];
|
||||
if (m_iPostJobCount > 0)
|
||||
{
|
||||
sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
szPostStatus[0] = 0;
|
||||
}
|
||||
BString<100> postStatus;
|
||||
if (m_postJobCount > 0)
|
||||
{
|
||||
postStatus.Format(", %i post-job%s", m_postJobCount, m_postJobCount > 1 ? "s" : "");
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
char* szControlSeq = "";
|
||||
const char* controlSeq = "";
|
||||
#else
|
||||
printf("\033[s");
|
||||
const char* szControlSeq = "\033[K";
|
||||
const char* controlSeq = "\033[K";
|
||||
#endif
|
||||
|
||||
snprintf(tmp, 1024, " %d threads, %.*f KB/s, %.2f MB remaining%s%s%s%s%s\n",
|
||||
m_iThreadCount, (iCurrentDownloadSpeed >= 10*1024 ? 0 : 1), (float)iCurrentDownloadSpeed / 1024.0,
|
||||
(float)(Util::Int64ToFloat(m_lRemainingSize) / 1024.0 / 1024.0), timeString, szPostStatus,
|
||||
m_bPauseDownload ? (m_bStandBy ? ", Paused" : ", Pausing") : "",
|
||||
szDownloadLimit, szControlSeq);
|
||||
tmp[1024-1] = '\0';
|
||||
printf("%s", tmp);
|
||||
m_bNeedGoBack = true;
|
||||
}
|
||||
BString<1024> status(" %d threads, %s, %s remaining%s%s%s%s%s\n",
|
||||
m_threadCount, *Util::FormatSpeed(currentDownloadSpeed),
|
||||
*Util::FormatSize(m_remainingSize), *timeString, *postStatus,
|
||||
m_pauseDownload ? (m_standBy ? ", Paused" : ", Pausing") : "",
|
||||
*downloadLimit, controlSeq);
|
||||
printf("%s", *status);
|
||||
m_needGoBack = true;
|
||||
}
|
||||
|
||||
void ColoredFrontend::PrintMessage(Message * pMessage)
|
||||
void ColoredFrontend::PrintMessage(Message& message)
|
||||
{
|
||||
#ifdef WIN32
|
||||
switch (pMessage->GetKind())
|
||||
switch (message.GetKind())
|
||||
{
|
||||
case Message::mkDebug:
|
||||
SetConsoleTextAttribute(m_hConsole, 8);
|
||||
SetConsoleTextAttribute(m_console, 8);
|
||||
printf("[DEBUG]");
|
||||
break;
|
||||
case Message::mkError:
|
||||
SetConsoleTextAttribute(m_hConsole, 4);
|
||||
SetConsoleTextAttribute(m_console, 4);
|
||||
printf("[ERROR]");
|
||||
break;
|
||||
break;
|
||||
case Message::mkWarning:
|
||||
SetConsoleTextAttribute(m_hConsole, 5);
|
||||
SetConsoleTextAttribute(m_console, 5);
|
||||
printf("[WARNING]");
|
||||
break;
|
||||
case Message::mkInfo:
|
||||
SetConsoleTextAttribute(m_hConsole, 2);
|
||||
SetConsoleTextAttribute(m_console, 2);
|
||||
printf("[INFO]");
|
||||
break;
|
||||
case Message::mkDetail:
|
||||
SetConsoleTextAttribute(m_hConsole, 2);
|
||||
SetConsoleTextAttribute(m_console, 2);
|
||||
printf("[DETAIL]");
|
||||
break;
|
||||
}
|
||||
SetConsoleTextAttribute(m_hConsole, 7);
|
||||
char* msg = strdup(pMessage->GetText());
|
||||
SetConsoleTextAttribute(m_console, 7);
|
||||
CString msg = message.GetText();
|
||||
CharToOem(msg, msg);
|
||||
printf(" %s\n", msg);
|
||||
free(msg);
|
||||
printf(" %s\n", *msg);
|
||||
#else
|
||||
const char* msg = pMessage->GetText();
|
||||
switch (pMessage->GetKind())
|
||||
const char* msg = message.GetText();
|
||||
switch (message.GetKind())
|
||||
{
|
||||
case Message::mkDebug:
|
||||
printf("[DEBUG] %s\033[K\n", msg);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file if part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,12 +15,7 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -32,22 +27,22 @@
|
||||
|
||||
class ColoredFrontend : public LoggableFrontend
|
||||
{
|
||||
private:
|
||||
bool m_bNeedGoBack;
|
||||
|
||||
#ifdef WIN32
|
||||
HANDLE m_hConsole;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void BeforePrint();
|
||||
virtual void PrintMessage(Message* pMessage);
|
||||
virtual void PrintStatus();
|
||||
virtual void PrintSkip();
|
||||
virtual void BeforeExit();
|
||||
|
||||
public:
|
||||
ColoredFrontend();
|
||||
|
||||
protected:
|
||||
virtual void BeforePrint();
|
||||
virtual void PrintMessage(Message& message);
|
||||
virtual void PrintStatus();
|
||||
virtual void PrintSkip();
|
||||
virtual void BeforeExit();
|
||||
|
||||
private:
|
||||
bool m_needGoBack = false;
|
||||
|
||||
#ifdef WIN32
|
||||
HANDLE m_console;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,34 +15,13 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "Frontend.h"
|
||||
#include "Log.h"
|
||||
#include "Connection.h"
|
||||
@@ -51,28 +30,26 @@
|
||||
#include "Util.h"
|
||||
#include "StatMeter.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
extern StatMeter* g_pStatMeter;
|
||||
|
||||
Frontend::Frontend()
|
||||
{
|
||||
debug("Creating Frontend");
|
||||
|
||||
m_iNeededLogFirstID = 0;
|
||||
m_iNeededLogEntries = 0;
|
||||
m_bSummary = false;
|
||||
m_bFileList = false;
|
||||
m_iCurrentDownloadSpeed = 0;
|
||||
m_lRemainingSize = 0;
|
||||
m_bPauseDownload = false;
|
||||
m_iDownloadLimit = 0;
|
||||
m_iThreadCount = 0;
|
||||
m_iPostJobCount = 0;
|
||||
m_iUpTimeSec = 0;
|
||||
m_iDnTimeSec = 0;
|
||||
m_iAllBytes = 0;
|
||||
m_bStandBy = 0;
|
||||
m_iUpdateInterval = g_pOptions->GetUpdateInterval();
|
||||
m_workStateObserver.m_owner = this;
|
||||
g_WorkState->Attach(&m_workStateObserver);
|
||||
|
||||
m_updateInterval = g_Options->GetUpdateInterval();
|
||||
}
|
||||
|
||||
void Frontend::Stop()
|
||||
{
|
||||
Thread::Stop();
|
||||
|
||||
m_waitCond.NotifyAll();
|
||||
}
|
||||
|
||||
void Frontend::WorkStateUpdate(Subject* caller, void* aspect)
|
||||
{
|
||||
m_waitCond.NotifyAll();
|
||||
}
|
||||
|
||||
bool Frontend::PrepareData()
|
||||
@@ -83,33 +60,31 @@ bool Frontend::PrepareData()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList()))
|
||||
if (!RequestMessages() || ((m_summary || m_fileList) && !RequestFileList()))
|
||||
{
|
||||
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", g_pOptions->GetControlIP(), g_pOptions->GetControlPort());
|
||||
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
|
||||
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", controlIp, g_Options->GetControlPort());
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bSummary)
|
||||
if (m_summary)
|
||||
{
|
||||
m_iCurrentDownloadSpeed = g_pStatMeter->CalcCurrentDownloadSpeed();
|
||||
m_bPauseDownload = g_pOptions->GetPauseDownload();
|
||||
m_iDownloadLimit = g_pOptions->GetDownloadRate();
|
||||
m_iThreadCount = Thread::GetThreadCount();
|
||||
g_pStatMeter->CalcTotalStat(&m_iUpTimeSec, &m_iDnTimeSec, &m_iAllBytes, &m_bStandBy);
|
||||
m_currentDownloadSpeed = g_StatMeter->CalcCurrentDownloadSpeed();
|
||||
m_pauseDownload = g_WorkState->GetPauseDownload();
|
||||
m_downloadLimit = g_WorkState->GetSpeedLimit();
|
||||
m_threadCount = Thread::GetThreadCount();
|
||||
g_StatMeter->CalcTotalStat(&m_upTimeSec, &m_dnTimeSec, &m_allBytes, &m_standBy);
|
||||
|
||||
DownloadQueue *pDownloadQueue = DownloadQueue::Lock();
|
||||
m_iPostJobCount = 0;
|
||||
for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++)
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
m_postJobCount = 0;
|
||||
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
||||
{
|
||||
NZBInfo* pNZBInfo = *it;
|
||||
m_iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0;
|
||||
m_postJobCount += nzbInfo->GetPostInfo() ? 1 : 0;
|
||||
}
|
||||
pDownloadQueue->CalcRemainingSize(&m_lRemainingSize, NULL);
|
||||
DownloadQueue::Unlock();
|
||||
|
||||
downloadQueue->CalcRemainingSize(&m_remainingSize, nullptr);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -119,110 +94,82 @@ void Frontend::FreeData()
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
for (Log::Messages::iterator it = m_RemoteMessages.begin(); it != m_RemoteMessages.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
m_RemoteMessages.clear();
|
||||
|
||||
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
|
||||
pDownloadQueue->GetQueue()->Clear();
|
||||
DownloadQueue::Unlock();
|
||||
m_remoteMessages.clear();
|
||||
DownloadQueue::Guard()->GetQueue()->clear();
|
||||
}
|
||||
}
|
||||
|
||||
Log::Messages* Frontend::LockMessages()
|
||||
GuardedMessageList Frontend::GuardMessages()
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
return &m_RemoteMessages;
|
||||
return GuardedMessageList(&m_remoteMessages, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_pLog->LockMessages();
|
||||
return g_Log->GuardMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void Frontend::UnlockMessages()
|
||||
{
|
||||
if (!IsRemoteMode())
|
||||
{
|
||||
g_pLog->UnlockMessages();
|
||||
}
|
||||
}
|
||||
|
||||
DownloadQueue* Frontend::LockQueue()
|
||||
{
|
||||
return DownloadQueue::Lock();
|
||||
}
|
||||
|
||||
void Frontend::UnlockQueue()
|
||||
{
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
|
||||
bool Frontend::IsRemoteMode()
|
||||
{
|
||||
return g_pOptions->GetRemoteClientMode();
|
||||
return g_Options->GetRemoteClientMode();
|
||||
}
|
||||
|
||||
void Frontend::ServerPauseUnpause(bool bPause)
|
||||
void Frontend::ServerPauseUnpause(bool pause)
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
RequestPauseUnpause(bPause);
|
||||
RequestPauseUnpause(pause);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_pOptions->SetResumeTime(0);
|
||||
g_pOptions->SetPauseDownload(bPause);
|
||||
g_WorkState->SetResumeTime(0);
|
||||
g_WorkState->SetPauseDownload(pause);
|
||||
}
|
||||
}
|
||||
|
||||
void Frontend::ServerSetDownloadRate(int iRate)
|
||||
void Frontend::ServerSetDownloadRate(int rate)
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
RequestSetDownloadRate(iRate);
|
||||
RequestSetDownloadRate(rate);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_pOptions->SetDownloadRate(iRate);
|
||||
g_WorkState->SetSpeedLimit(rate);
|
||||
}
|
||||
}
|
||||
|
||||
bool Frontend::ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID)
|
||||
bool Frontend::ServerEditQueue(DownloadQueue::EEditAction action, int offset, int id)
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
return RequestEditQueue(eAction, iOffset, iID);
|
||||
return RequestEditQueue(action, offset, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadQueue* pDownloadQueue = LockQueue();
|
||||
bool bOK = pDownloadQueue->EditEntry(iID, eAction, iOffset, NULL);
|
||||
UnlockQueue();
|
||||
return bOK;
|
||||
return DownloadQueue::Guard()->EditEntry(id, action, CString::FormatStr("%i", offset));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
|
||||
void Frontend::InitMessageBase(SNzbRequestBase* messageBase, int request, int size)
|
||||
{
|
||||
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
|
||||
pMessageBase->m_iType = htonl(iRequest);
|
||||
pMessageBase->m_iStructSize = htonl(iSize);
|
||||
messageBase->m_signature = htonl(NZBMESSAGE_SIGNATURE);
|
||||
messageBase->m_type = htonl(request);
|
||||
messageBase->m_structSize = htonl(size);
|
||||
|
||||
strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
|
||||
pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0';
|
||||
strncpy(messageBase->m_username, g_Options->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
|
||||
messageBase->m_username[NZBREQUESTPASSWORDSIZE - 1] = '\0';
|
||||
|
||||
strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE);
|
||||
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
|
||||
strncpy(messageBase->m_password, g_Options->GetControlPassword(), NZBREQUESTPASSWORDSIZE);
|
||||
messageBase->m_password[NZBREQUESTPASSWORDSIZE - 1] = '\0';
|
||||
}
|
||||
|
||||
bool Frontend::RequestMessages()
|
||||
{
|
||||
Connection connection(g_pOptions->GetControlIP(), g_pOptions->GetControlPort(), false);
|
||||
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
|
||||
Connection connection(controlIp, g_Options->GetControlPort(), false);
|
||||
|
||||
bool OK = connection.Connect();
|
||||
if (!OK)
|
||||
@@ -230,16 +177,16 @@ bool Frontend::RequestMessages()
|
||||
return false;
|
||||
}
|
||||
|
||||
SNZBLogRequest LogRequest;
|
||||
InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest));
|
||||
LogRequest.m_iLines = htonl(m_iNeededLogEntries);
|
||||
if (m_iNeededLogEntries == 0)
|
||||
SNzbLogRequest LogRequest;
|
||||
InitMessageBase(&LogRequest.m_messageBase, rrLog, sizeof(LogRequest));
|
||||
LogRequest.m_lines = htonl(m_neededLogEntries);
|
||||
if (m_neededLogEntries == 0)
|
||||
{
|
||||
LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1);
|
||||
LogRequest.m_idFrom = htonl(m_neededLogFirstId > 0 ? m_neededLogFirstId : 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogRequest.m_iIDFrom = 0;
|
||||
LogRequest.m_idFrom = 0;
|
||||
}
|
||||
|
||||
if (!connection.Send((char*)(&LogRequest), sizeof(LogRequest)))
|
||||
@@ -248,44 +195,40 @@ bool Frontend::RequestMessages()
|
||||
}
|
||||
|
||||
// Now listen for the returned log
|
||||
SNZBLogResponse LogResponse;
|
||||
bool bRead = connection.Recv((char*) &LogResponse, sizeof(LogResponse));
|
||||
if (!bRead ||
|
||||
(int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
|
||||
ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse))
|
||||
SNzbLogResponse LogResponse;
|
||||
bool read = connection.Recv((char*) &LogResponse, sizeof(LogResponse));
|
||||
if (!read ||
|
||||
(int)ntohl(LogResponse.m_messageBase.m_signature) != (int)NZBMESSAGE_SIGNATURE ||
|
||||
ntohl(LogResponse.m_messageBase.m_structSize) != sizeof(LogResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* pBuf = NULL;
|
||||
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
|
||||
CharBuffer buf;
|
||||
if (ntohl(LogResponse.m_trailingDataLength) > 0)
|
||||
{
|
||||
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
|
||||
if (!connection.Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
|
||||
buf.Reserve(ntohl(LogResponse.m_trailingDataLength));
|
||||
if (!connection.Recv(buf, buf.Size()))
|
||||
{
|
||||
free(pBuf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
connection.Disconnect();
|
||||
|
||||
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
|
||||
if (ntohl(LogResponse.m_trailingDataLength) > 0)
|
||||
{
|
||||
char* pBufPtr = (char*)pBuf;
|
||||
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
|
||||
char* bufPtr = (char*)buf;
|
||||
for (uint32 i = 0; i < ntohl(LogResponse.m_nrTrailingEntries); i++)
|
||||
{
|
||||
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
|
||||
SNzbLogResponseEntry* logAnswer = (SNzbLogResponseEntry*) bufPtr;
|
||||
|
||||
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
|
||||
char* text = bufPtr + sizeof(SNzbLogResponseEntry);
|
||||
|
||||
Message* pMessage = new Message(ntohl(pLogAnswer->m_iID), (Message::EKind)ntohl(pLogAnswer->m_iKind), ntohl(pLogAnswer->m_tTime), szText);
|
||||
m_RemoteMessages.push_back(pMessage);
|
||||
m_remoteMessages.emplace_back(ntohl(logAnswer->m_id), (Message::EKind)ntohl(logAnswer->m_kind), ntohl(logAnswer->m_time), text);
|
||||
|
||||
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
|
||||
bufPtr += sizeof(SNzbLogResponseEntry) + ntohl(logAnswer->m_textLen);
|
||||
}
|
||||
|
||||
free(pBuf);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -293,7 +236,8 @@ bool Frontend::RequestMessages()
|
||||
|
||||
bool Frontend::RequestFileList()
|
||||
{
|
||||
Connection connection(g_pOptions->GetControlIP(), g_pOptions->GetControlPort(), false);
|
||||
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
|
||||
Connection connection(controlIp, g_Options->GetControlPort(), false);
|
||||
|
||||
bool OK = connection.Connect();
|
||||
if (!OK)
|
||||
@@ -301,10 +245,10 @@ bool Frontend::RequestFileList()
|
||||
return false;
|
||||
}
|
||||
|
||||
SNZBListRequest ListRequest;
|
||||
InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest));
|
||||
ListRequest.m_bFileList = htonl(m_bFileList);
|
||||
ListRequest.m_bServerState = htonl(m_bSummary);
|
||||
SNzbListRequest ListRequest;
|
||||
InitMessageBase(&ListRequest.m_messageBase, rrList, sizeof(ListRequest));
|
||||
ListRequest.m_fileList = htonl(m_fileList);
|
||||
ListRequest.m_serverState = htonl(m_summary);
|
||||
|
||||
if (!connection.Send((char*)(&ListRequest), sizeof(ListRequest)))
|
||||
{
|
||||
@@ -312,77 +256,83 @@ bool Frontend::RequestFileList()
|
||||
}
|
||||
|
||||
// Now listen for the returned list
|
||||
SNZBListResponse ListResponse;
|
||||
bool bRead = connection.Recv((char*) &ListResponse, sizeof(ListResponse));
|
||||
if (!bRead ||
|
||||
(int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
|
||||
ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse))
|
||||
SNzbListResponse ListResponse;
|
||||
bool read = connection.Recv((char*) &ListResponse, sizeof(ListResponse));
|
||||
if (!read ||
|
||||
(int)ntohl(ListResponse.m_messageBase.m_signature) != (int)NZBMESSAGE_SIGNATURE ||
|
||||
ntohl(ListResponse.m_messageBase.m_structSize) != sizeof(ListResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* pBuf = NULL;
|
||||
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
|
||||
CharBuffer buf;
|
||||
if (ntohl(ListResponse.m_trailingDataLength) > 0)
|
||||
{
|
||||
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
|
||||
if (!connection.Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
|
||||
buf.Reserve(ntohl(ListResponse.m_trailingDataLength));
|
||||
if (!connection.Recv(buf, buf.Size()))
|
||||
{
|
||||
free(pBuf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
connection.Disconnect();
|
||||
|
||||
if (m_bSummary)
|
||||
if (m_summary)
|
||||
{
|
||||
m_bPauseDownload = ntohl(ListResponse.m_bDownloadPaused);
|
||||
m_lRemainingSize = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo));
|
||||
m_iCurrentDownloadSpeed = ntohl(ListResponse.m_iDownloadRate);
|
||||
m_iDownloadLimit = ntohl(ListResponse.m_iDownloadLimit);
|
||||
m_iThreadCount = ntohl(ListResponse.m_iThreadCount);
|
||||
m_iPostJobCount = ntohl(ListResponse.m_iPostJobCount);
|
||||
m_iUpTimeSec = ntohl(ListResponse.m_iUpTimeSec);
|
||||
m_iDnTimeSec = ntohl(ListResponse.m_iDownloadTimeSec);
|
||||
m_bStandBy = ntohl(ListResponse.m_bDownloadStandBy);
|
||||
m_iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo));
|
||||
m_pauseDownload = ntohl(ListResponse.m_downloadPaused);
|
||||
m_remainingSize = Util::JoinInt64(ntohl(ListResponse.m_remainingSizeHi), ntohl(ListResponse.m_remainingSizeLo));
|
||||
m_currentDownloadSpeed = ntohl(ListResponse.m_downloadRate);
|
||||
m_downloadLimit = ntohl(ListResponse.m_downloadLimit);
|
||||
m_threadCount = ntohl(ListResponse.m_threadCount);
|
||||
m_postJobCount = ntohl(ListResponse.m_postJobCount);
|
||||
m_upTimeSec = ntohl(ListResponse.m_upTimeSec);
|
||||
m_dnTimeSec = ntohl(ListResponse.m_downloadTimeSec);
|
||||
m_standBy = ntohl(ListResponse.m_downloadStandBy);
|
||||
m_allBytes = Util::JoinInt64(ntohl(ListResponse.m_downloadedBytesHi), ntohl(ListResponse.m_downloadedBytesLo));
|
||||
}
|
||||
|
||||
if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0)
|
||||
if (m_fileList && ntohl(ListResponse.m_trailingDataLength) > 0)
|
||||
{
|
||||
RemoteClient client;
|
||||
client.SetVerbose(false);
|
||||
|
||||
DownloadQueue* pDownloadQueue = LockQueue();
|
||||
client.BuildFileList(&ListResponse, pBuf, pDownloadQueue);
|
||||
UnlockQueue();
|
||||
}
|
||||
|
||||
if (pBuf)
|
||||
{
|
||||
free(pBuf);
|
||||
client.BuildFileList(&ListResponse, buf, DownloadQueue::Guard());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Frontend::RequestPauseUnpause(bool bPause)
|
||||
bool Frontend::RequestPauseUnpause(bool pause)
|
||||
{
|
||||
RemoteClient client;
|
||||
client.SetVerbose(false);
|
||||
return client.RequestServerPauseUnpause(bPause, eRemotePauseUnpauseActionDownload);
|
||||
return client.RequestServerPauseUnpause(pause, rpDownload);
|
||||
}
|
||||
|
||||
bool Frontend::RequestSetDownloadRate(int iRate)
|
||||
bool Frontend::RequestSetDownloadRate(int rate)
|
||||
{
|
||||
RemoteClient client;
|
||||
client.SetVerbose(false);
|
||||
return client.RequestServerSetDownloadRate(iRate);
|
||||
return client.RequestServerSetDownloadRate(rate);
|
||||
}
|
||||
|
||||
bool Frontend::RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID)
|
||||
bool Frontend::RequestEditQueue(DownloadQueue::EEditAction action, int offset, int id)
|
||||
{
|
||||
RemoteClient client;
|
||||
client.SetVerbose(false);
|
||||
return client.RequestServerEditQueue(eAction, iOffset, NULL, &iID, 1, NULL, eRemoteMatchModeID);
|
||||
IdList ids = { id };
|
||||
return client.RequestServerEditQueue(action, offset, nullptr, &ids, nullptr, rmId);
|
||||
}
|
||||
|
||||
void Frontend::Wait(int milliseconds)
|
||||
{
|
||||
if (g_WorkState->GetPauseFrontend())
|
||||
{
|
||||
Guard guard(m_waitMutex);
|
||||
m_waitCond.WaitFor(m_waitMutex, 2000);
|
||||
}
|
||||
else
|
||||
{
|
||||
Util::Sleep(milliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,12 +15,7 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -32,51 +27,62 @@
|
||||
#include "DownloadInfo.h"
|
||||
#include "MessageBase.h"
|
||||
#include "QueueEditor.h"
|
||||
#include "Observer.h"
|
||||
|
||||
class Frontend : public Thread
|
||||
{
|
||||
private:
|
||||
Log::Messages m_RemoteMessages;
|
||||
|
||||
bool RequestMessages();
|
||||
bool RequestFileList();
|
||||
public:
|
||||
Frontend();
|
||||
|
||||
protected:
|
||||
bool m_bSummary;
|
||||
bool m_bFileList;
|
||||
unsigned int m_iNeededLogEntries;
|
||||
unsigned int m_iNeededLogFirstID;
|
||||
int m_iUpdateInterval;
|
||||
bool m_summary = false;
|
||||
bool m_fileList = false;
|
||||
uint32 m_neededLogEntries = 0;
|
||||
uint32 m_neededLogFirstId = 0;
|
||||
int m_updateInterval;
|
||||
|
||||
// summary
|
||||
int m_iCurrentDownloadSpeed;
|
||||
long long m_lRemainingSize;
|
||||
bool m_bPauseDownload;
|
||||
int m_iDownloadLimit;
|
||||
int m_iThreadCount;
|
||||
int m_iPostJobCount;
|
||||
int m_iUpTimeSec;
|
||||
int m_iDnTimeSec;
|
||||
long long m_iAllBytes;
|
||||
bool m_bStandBy;
|
||||
int m_currentDownloadSpeed = 0;
|
||||
int64 m_remainingSize = 0;
|
||||
bool m_pauseDownload = false;
|
||||
int m_downloadLimit = 0;
|
||||
int m_threadCount = 0;
|
||||
int m_postJobCount = 0;
|
||||
int m_upTimeSec = 0;
|
||||
int m_dnTimeSec = 0;
|
||||
int64 m_allBytes = 0;
|
||||
bool m_standBy = false;
|
||||
Mutex m_waitMutex;
|
||||
ConditionVar m_waitCond;
|
||||
|
||||
bool PrepareData();
|
||||
void FreeData();
|
||||
Log::Messages* LockMessages();
|
||||
void UnlockMessages();
|
||||
DownloadQueue* LockQueue();
|
||||
void UnlockQueue();
|
||||
bool IsRemoteMode();
|
||||
void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize);
|
||||
void ServerPauseUnpause(bool bPause);
|
||||
bool RequestPauseUnpause(bool bPause);
|
||||
void ServerSetDownloadRate(int iRate);
|
||||
bool RequestSetDownloadRate(int iRate);
|
||||
bool ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iEntry);
|
||||
bool RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID);
|
||||
virtual void Stop();
|
||||
bool PrepareData();
|
||||
void FreeData();
|
||||
GuardedMessageList GuardMessages();
|
||||
bool IsRemoteMode();
|
||||
void InitMessageBase(SNzbRequestBase* messageBase, int request, int size);
|
||||
void ServerPauseUnpause(bool pause);
|
||||
bool RequestPauseUnpause(bool pause);
|
||||
void ServerSetDownloadRate(int rate);
|
||||
bool RequestSetDownloadRate(int rate);
|
||||
bool ServerEditQueue(DownloadQueue::EEditAction action, int offset, int entry);
|
||||
bool RequestEditQueue(DownloadQueue::EEditAction action, int offset, int id);
|
||||
void Wait(int milliseconds);
|
||||
|
||||
public:
|
||||
Frontend();
|
||||
private:
|
||||
class WorkStateObserver : public Observer
|
||||
{
|
||||
public:
|
||||
Frontend* m_owner;
|
||||
virtual void Update(Subject* caller, void* aspect) { m_owner->WorkStateUpdate(caller, aspect); }
|
||||
};
|
||||
|
||||
MessageList m_remoteMessages;
|
||||
WorkStateObserver m_workStateObserver;
|
||||
|
||||
bool RequestMessages();
|
||||
bool RequestFileList();
|
||||
void WorkStateUpdate(Subject* caller, void* aspect);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file if part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,43 +15,15 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Util.h"
|
||||
#include "LoggableFrontend.h"
|
||||
#include "Log.h"
|
||||
|
||||
LoggableFrontend::LoggableFrontend()
|
||||
{
|
||||
debug("Creating LoggableFrontend");
|
||||
|
||||
m_iNeededLogEntries = 0;
|
||||
m_bSummary = false;
|
||||
m_bFileList = false;
|
||||
}
|
||||
|
||||
void LoggableFrontend::Run()
|
||||
{
|
||||
debug("Entering LoggableFrontend-loop");
|
||||
@@ -59,7 +31,7 @@ void LoggableFrontend::Run()
|
||||
while (!IsStopped())
|
||||
{
|
||||
Update();
|
||||
usleep(m_iUpdateInterval * 1000);
|
||||
Wait(m_updateInterval);
|
||||
}
|
||||
// Printing the last messages
|
||||
Update();
|
||||
@@ -79,23 +51,24 @@ void LoggableFrontend::Update()
|
||||
|
||||
BeforePrint();
|
||||
|
||||
Log::Messages* pMessages = LockMessages();
|
||||
if (!pMessages->empty())
|
||||
{
|
||||
Message* pFirstMessage = pMessages->front();
|
||||
int iStart = m_iNeededLogFirstID - pFirstMessage->GetID() + 1;
|
||||
if (iStart < 0)
|
||||
GuardedMessageList messages = GuardMessages();
|
||||
if (!messages->empty())
|
||||
{
|
||||
PrintSkip();
|
||||
iStart = 0;
|
||||
}
|
||||
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
|
||||
{
|
||||
PrintMessage((*pMessages)[i]);
|
||||
m_iNeededLogFirstID = (*pMessages)[i]->GetID();
|
||||
Message& firstMessage = messages->front();
|
||||
int start = m_neededLogFirstId - firstMessage.GetId() + 1;
|
||||
if (start < 0)
|
||||
{
|
||||
PrintSkip();
|
||||
start = 0;
|
||||
}
|
||||
for (uint32 i = (uint32)start; i < messages->size(); i++)
|
||||
{
|
||||
PrintMessage(messages->at(i));
|
||||
m_neededLogFirstId = messages->at(i).GetId();
|
||||
}
|
||||
}
|
||||
}
|
||||
UnlockMessages();
|
||||
|
||||
PrintStatus();
|
||||
|
||||
@@ -104,15 +77,16 @@ void LoggableFrontend::Update()
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void LoggableFrontend::PrintMessage(Message * pMessage)
|
||||
void LoggableFrontend::PrintMessage(Message& message)
|
||||
{
|
||||
#ifdef WIN32
|
||||
char* msg = strdup(pMessage->GetText());
|
||||
CharToOem(msg, msg);
|
||||
CString cmsg = message.GetText();
|
||||
CharToOem(cmsg, cmsg);
|
||||
const char* msg = cmsg;
|
||||
#else
|
||||
const char* msg = pMessage->GetText();
|
||||
const char* msg = message.GetText();
|
||||
#endif
|
||||
switch (pMessage->GetKind())
|
||||
switch (message.GetKind())
|
||||
{
|
||||
case Message::mkDebug:
|
||||
printf("[DEBUG] %s\n", msg);
|
||||
@@ -130,9 +104,6 @@ void LoggableFrontend::PrintMessage(Message * pMessage)
|
||||
printf("[DETAIL] %s\n", msg);
|
||||
break;
|
||||
}
|
||||
#ifdef WIN32
|
||||
free(msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoggableFrontend::PrintSkip()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file if part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,12 +15,7 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -33,16 +28,13 @@
|
||||
class LoggableFrontend : public Frontend
|
||||
{
|
||||
protected:
|
||||
|
||||
virtual void Run();
|
||||
virtual void Update();
|
||||
virtual void BeforePrint() {};
|
||||
virtual void BeforeExit() {};
|
||||
virtual void PrintMessage(Message* pMessage);
|
||||
virtual void PrintStatus() {};
|
||||
virtual void PrintSkip();
|
||||
public:
|
||||
LoggableFrontend();
|
||||
virtual void Run();
|
||||
virtual void Update();
|
||||
virtual void BeforePrint() {};
|
||||
virtual void BeforeExit() {};
|
||||
virtual void PrintMessage(Message& message);
|
||||
virtual void PrintStatus() {};
|
||||
virtual void PrintSkip();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,12 +15,7 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -29,97 +24,93 @@
|
||||
|
||||
#ifndef DISABLE_CURSES
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Frontend.h"
|
||||
#include "Log.h"
|
||||
#include "DownloadInfo.h"
|
||||
|
||||
class NCursesFrontend : public Frontend
|
||||
{
|
||||
private:
|
||||
|
||||
enum EInputMode
|
||||
{
|
||||
eNormal,
|
||||
eEditQueue,
|
||||
eDownloadRate
|
||||
};
|
||||
|
||||
bool m_bUseColor;
|
||||
int m_iDataUpdatePos;
|
||||
bool m_bUpdateNextTime;
|
||||
int m_iScreenHeight;
|
||||
int m_iScreenWidth;
|
||||
int m_iQueueWinTop;
|
||||
int m_iQueueWinHeight;
|
||||
int m_iQueueWinClientHeight;
|
||||
int m_iMessagesWinTop;
|
||||
int m_iMessagesWinHeight;
|
||||
int m_iMessagesWinClientHeight;
|
||||
int m_iSelectedQueueEntry;
|
||||
int m_iLastEditEntry;
|
||||
bool m_bLastPausePars;
|
||||
int m_iQueueScrollOffset;
|
||||
char* m_szHint;
|
||||
time_t m_tStartHint;
|
||||
int m_iColWidthFiles;
|
||||
int m_iColWidthTotal;
|
||||
int m_iColWidthLeft;
|
||||
|
||||
// Inputting numbers
|
||||
int m_iInputNumberIndex;
|
||||
int m_iInputValue;
|
||||
|
||||
#ifdef WIN32
|
||||
CHAR_INFO* m_pScreenBuffer;
|
||||
CHAR_INFO* m_pOldScreenBuffer;
|
||||
int m_iScreenBufferSize;
|
||||
std::vector<WORD> m_ColorAttr;
|
||||
#else
|
||||
void* m_pWindow; // WINDOW*
|
||||
#endif
|
||||
|
||||
EInputMode m_eInputMode;
|
||||
bool m_bShowNZBname;
|
||||
bool m_bShowTimestamp;
|
||||
bool m_bGroupFiles;
|
||||
float m_QueueWindowPercentage;
|
||||
|
||||
#ifdef WIN32
|
||||
void init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor);
|
||||
#endif
|
||||
void PlotLine(const char * szString, int iRow, int iPos, int iColorPair);
|
||||
void PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink);
|
||||
void PrintMessages();
|
||||
void PrintQueue();
|
||||
void PrintFileQueue();
|
||||
void PrintFilename(FileInfo* pFileInfo, int iRow, bool bSelected);
|
||||
void PrintGroupQueue();
|
||||
void ResetColWidths();
|
||||
void PrintGroupname(NZBInfo* pNZBInfo, int iRow, bool bSelected, bool bCalcColWidth);
|
||||
void PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime);
|
||||
int PrintMessage(Message* Msg, int iRow, int iMaxLines);
|
||||
void PrintKeyInputBar();
|
||||
void PrintStatus();
|
||||
void UpdateInput(int initialKey);
|
||||
void Update(int iKey);
|
||||
void SetCurrentQueueEntry(int iEntry);
|
||||
void CalcWindowSizes();
|
||||
void RefreshScreen();
|
||||
int ReadConsoleKey();
|
||||
int CalcQueueSize();
|
||||
void NeedUpdateData();
|
||||
bool EditQueue(DownloadQueue::EEditAction eAction, int iOffset);
|
||||
void SetHint(const char* szHint);
|
||||
public:
|
||||
NCursesFrontend();
|
||||
virtual ~NCursesFrontend();
|
||||
|
||||
protected:
|
||||
virtual void Run();
|
||||
virtual void Run();
|
||||
|
||||
public:
|
||||
NCursesFrontend();
|
||||
virtual ~NCursesFrontend();
|
||||
private:
|
||||
enum EInputMode
|
||||
{
|
||||
normal,
|
||||
editQueue,
|
||||
downloadRate
|
||||
};
|
||||
|
||||
bool m_useColor = true;
|
||||
int m_dataUpdatePos = 0;
|
||||
bool m_updateNextTime = false;
|
||||
int m_screenHeight = 0;
|
||||
int m_screenWidth = 0;
|
||||
int m_queueWinTop = 0;
|
||||
int m_queueWinHeight = 0;
|
||||
int m_queueWinClientHeight = 0;
|
||||
int m_messagesWinTop = 0;
|
||||
int m_messagesWinHeight = 0;
|
||||
int m_messagesWinClientHeight = 0;
|
||||
int m_selectedQueueEntry = 0;
|
||||
int m_lastEditEntry = -1;
|
||||
bool m_lastPausePars = false;
|
||||
int m_queueScrollOffset = 0;
|
||||
CString m_hint;
|
||||
time_t m_startHint;
|
||||
int m_colWidthFiles;
|
||||
int m_colWidthTotal;
|
||||
int m_colWidthLeft;
|
||||
|
||||
// Inputting numbers
|
||||
int m_inputNumberIndex = 0;
|
||||
int m_inputValue;
|
||||
|
||||
#ifdef WIN32
|
||||
std::vector<CHAR_INFO> m_screenBuffer;
|
||||
std::vector<CHAR_INFO> m_oldScreenBuffer;
|
||||
std::vector<WORD> m_colorAttr;
|
||||
#else
|
||||
void* m_window; // WINDOW*
|
||||
#endif
|
||||
|
||||
EInputMode m_inputMode = normal;
|
||||
bool m_showNzbname;
|
||||
bool m_showTimestamp;
|
||||
bool m_groupFiles;
|
||||
int m_queueWindowPercentage = 50;
|
||||
|
||||
#ifdef WIN32
|
||||
void init_pair(int colorNumber, WORD wForeColor, WORD wBackColor);
|
||||
#endif
|
||||
void PlotLine(const char * string, int row, int pos, int colorPair);
|
||||
void PlotText(const char * string, int row, int pos, int colorPair, bool blink);
|
||||
void PrintMessages();
|
||||
void PrintQueue();
|
||||
void PrintFileQueue();
|
||||
void PrintFilename(FileInfo* fileInfo, int row, bool selected);
|
||||
void PrintGroupQueue();
|
||||
void ResetColWidths();
|
||||
void PrintGroupname(NzbInfo* nzbInfo, int row, bool selected, bool calcColWidth);
|
||||
void PrintTopHeader(char* header, int lineNr, bool upTime);
|
||||
int PrintMessage(Message& msg, int row, int maxLines);
|
||||
void PrintKeyInputBar();
|
||||
void PrintStatus();
|
||||
void UpdateInput(int initialKey);
|
||||
void Update(int key);
|
||||
void SetCurrentQueueEntry(int entry);
|
||||
void CalcWindowSizes();
|
||||
void RefreshScreen();
|
||||
int ReadConsoleKey();
|
||||
int CalcQueueSize();
|
||||
void NeedUpdateData();
|
||||
bool EditQueue(DownloadQueue::EEditAction action, int offset);
|
||||
void SetHint(const char* hint);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
956
daemon/main/CommandLineParser.cpp
Normal file
956
daemon/main/CommandLineParser.cpp
Normal file
@@ -0,0 +1,956 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "CommandLineParser.h"
|
||||
#include "Log.h"
|
||||
#include "MessageBase.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Util.h"
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"configfile", required_argument, 0, 'c'},
|
||||
{"noconfigfile", no_argument, 0, 'n'},
|
||||
{"printconfig", no_argument, 0, 'p'},
|
||||
{"server", no_argument, 0, 's' },
|
||||
{"daemon", no_argument, 0, 'D' },
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"serverversion", no_argument, 0, 'V'},
|
||||
{"option", required_argument, 0, 'o'},
|
||||
{"append", no_argument, 0, 'A'},
|
||||
{"list", no_argument, 0, 'L'},
|
||||
{"pause", no_argument, 0, 'P'},
|
||||
{"unpause", no_argument, 0, 'U'},
|
||||
{"rate", required_argument, 0, 'R'},
|
||||
{"system", no_argument, 0, 'B'},
|
||||
{"log", required_argument, 0, 'G'},
|
||||
{"top", no_argument, 0, 'T'},
|
||||
{"edit", required_argument, 0, 'E'},
|
||||
{"connect", no_argument, 0, 'C'},
|
||||
{"quit", no_argument, 0, 'Q'},
|
||||
{"reload", no_argument, 0, 'O'},
|
||||
{"write", required_argument, 0, 'W'},
|
||||
{"category", required_argument, 0, 'K'},
|
||||
{"scan", no_argument, 0, 'S'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
#endif
|
||||
|
||||
static char short_options[] = "c:hno:psvAB:DCE:G:K:LPR:STUQOVW:";
|
||||
|
||||
|
||||
CommandLineParser::CommandLineParser(int argc, const char* argv[])
|
||||
{
|
||||
InitCommandLine(argc, argv);
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
m_printUsage = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_printOptions && !m_printUsage && !m_printVersion)
|
||||
{
|
||||
InitFileArg(argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::InitCommandLine(int argc, const char* const_argv[])
|
||||
{
|
||||
m_clientOperation = opClientNoOperation; // default
|
||||
|
||||
std::vector<CString> argv;
|
||||
argv.reserve(argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
argv.emplace_back(const_argv[i]);
|
||||
}
|
||||
|
||||
// reset getopt
|
||||
optind = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int c;
|
||||
|
||||
#ifdef HAVE_GETOPT_LONG
|
||||
int option_index = 0;
|
||||
c = getopt_long(argc, (char**)argv.data(), short_options, long_options, &option_index);
|
||||
#else
|
||||
c = getopt(argc, (char**)argv.data(), short_options);
|
||||
#endif
|
||||
|
||||
if (c == -1) break;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'c':
|
||||
m_configFilename = optarg;
|
||||
break;
|
||||
case 'n':
|
||||
m_configFilename = nullptr;
|
||||
m_noConfig = true;
|
||||
break;
|
||||
case 'h':
|
||||
m_printUsage = true;
|
||||
return;
|
||||
case 'v':
|
||||
m_printVersion = true;
|
||||
return;
|
||||
case 'p':
|
||||
m_printOptions = true;
|
||||
break;
|
||||
case 'o':
|
||||
m_optionList.push_back(optarg);
|
||||
break;
|
||||
case 's':
|
||||
m_serverMode = true;
|
||||
break;
|
||||
case 'D':
|
||||
m_serverMode = true;
|
||||
m_daemonMode = true;
|
||||
break;
|
||||
case 'A':
|
||||
m_clientOperation = opClientRequestDownload;
|
||||
|
||||
while (true)
|
||||
{
|
||||
optind++;
|
||||
optarg = optind > argc ? nullptr : (char*)argv[optind-1];
|
||||
if (optarg && (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "U")))
|
||||
{
|
||||
// option ignored (but kept for compatibility)
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "T"))
|
||||
{
|
||||
m_addTop = true;
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "P"))
|
||||
{
|
||||
m_addPaused = true;
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "I"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
m_addPriority = atoi(argv[optind-1]);
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "C"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
m_addCategory = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "N"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
m_addNzbFilename = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "DK"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
m_addDupeKey = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "DS"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
m_addDupeScore = atoi(argv[optind-1]);
|
||||
}
|
||||
else if (optarg && !strcasecmp(optarg, "DM"))
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* dupeMode = argv[optind-1];
|
||||
if (!strcasecmp(dupeMode, "score"))
|
||||
{
|
||||
m_addDupeMode = dmScore;
|
||||
}
|
||||
else if (!strcasecmp(dupeMode, "all"))
|
||||
{
|
||||
m_addDupeMode = dmAll;
|
||||
}
|
||||
else if (!strcasecmp(dupeMode, "force"))
|
||||
{
|
||||
m_addDupeMode = dmForce;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'A'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
optind--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
optind++;
|
||||
optarg = optind > argc ? nullptr : (char*)argv[optind-1];
|
||||
if (!optarg || !strncmp(optarg, "-", 1))
|
||||
{
|
||||
m_clientOperation = opClientRequestListFiles;
|
||||
optind--;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "FR"))
|
||||
{
|
||||
m_clientOperation = opClientRequestListFiles;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "G") || !strcasecmp(optarg, "GR"))
|
||||
{
|
||||
m_clientOperation = opClientRequestListGroups;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "O"))
|
||||
{
|
||||
m_clientOperation = opClientRequestPostQueue;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "S"))
|
||||
{
|
||||
m_clientOperation = opClientRequestListStatus;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "H"))
|
||||
{
|
||||
m_clientOperation = opClientRequestHistory;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "HA"))
|
||||
{
|
||||
m_clientOperation = opClientRequestHistoryAll;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'L'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (optarg && (!strcasecmp(optarg, "FR") || !strcasecmp(optarg, "GR")))
|
||||
{
|
||||
m_matchMode = mmRegEx;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'L'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
}
|
||||
break;
|
||||
case 'P':
|
||||
case 'U':
|
||||
optind++;
|
||||
optarg = optind > argc ? nullptr : (char*)argv[optind-1];
|
||||
if (!optarg || !strncmp(optarg, "-", 1))
|
||||
{
|
||||
m_clientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause;
|
||||
optind--;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "D"))
|
||||
{
|
||||
m_clientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "O"))
|
||||
{
|
||||
m_clientOperation = c == 'P' ? opClientRequestPostPause : opClientRequestPostUnpause;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "S"))
|
||||
{
|
||||
m_clientOperation = c == 'P' ? opClientRequestScanPause : opClientRequestScanUnpause;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError(c == 'P' ? "Could not parse value of option 'P'\n" : "Could not parse value of option 'U'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
m_clientOperation = opClientRequestSetRate;
|
||||
m_setRate = (int)(atof(optarg)*1024);
|
||||
break;
|
||||
case 'B':
|
||||
if (!strcasecmp(optarg, "dump"))
|
||||
{
|
||||
m_clientOperation = opClientRequestDumpDebug;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "trace"))
|
||||
{
|
||||
m_testBacktrace = true;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "webget"))
|
||||
{
|
||||
m_webGet = true;
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'B'");
|
||||
return;
|
||||
}
|
||||
optarg = argv[optind-1];
|
||||
m_webGetFilename = optarg;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "verify"))
|
||||
{
|
||||
m_sigVerify = true;
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'B'");
|
||||
return;
|
||||
}
|
||||
optarg = argv[optind-1];
|
||||
m_pubKeyFilename = optarg;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'B'");
|
||||
return;
|
||||
}
|
||||
optarg = argv[optind-1];
|
||||
m_sigFilename = optarg;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'B'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'G':
|
||||
m_clientOperation = opClientRequestLog;
|
||||
m_logLines = atoi(optarg);
|
||||
if (m_logLines == 0)
|
||||
{
|
||||
ReportError("Could not parse value of option 'G'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
m_addTop = true;
|
||||
break;
|
||||
case 'C':
|
||||
m_remoteClientMode = true;
|
||||
break;
|
||||
case 'E':
|
||||
{
|
||||
m_clientOperation = opClientRequestEditQueue;
|
||||
bool group = !strcasecmp(optarg, "G") || !strcasecmp(optarg, "GN") || !strcasecmp(optarg, "GR");
|
||||
bool file = !strcasecmp(optarg, "F") || !strcasecmp(optarg, "FN") || !strcasecmp(optarg, "FR");
|
||||
if (!strcasecmp(optarg, "GN") || !strcasecmp(optarg, "FN"))
|
||||
{
|
||||
m_matchMode = mmName;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "GR") || !strcasecmp(optarg, "FR"))
|
||||
{
|
||||
m_matchMode = mmRegEx;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_matchMode = mmId;
|
||||
};
|
||||
bool post = !strcasecmp(optarg, "O");
|
||||
bool history = !strcasecmp(optarg, "H");
|
||||
if (group || file || post || history)
|
||||
{
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
optarg = argv[optind-1];
|
||||
}
|
||||
|
||||
if (post)
|
||||
{
|
||||
// edit-commands for post-processor-queue
|
||||
if (!strcasecmp(optarg, "D"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaPostDelete;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (history)
|
||||
{
|
||||
// edit-commands for history
|
||||
if (!strcasecmp(optarg, "D"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryDelete;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "R"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryReturn;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "P"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryProcess;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "A"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryRedownload;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "F"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryRetryFailed;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "O"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistorySetParameter;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
|
||||
if (!strchr(m_editQueueText, '='))
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!strcasecmp(optarg, "B"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryMarkBad;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "G"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryMarkGood;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "S"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaHistoryMarkSuccess;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// edit-commands for download-queue
|
||||
if (!strcasecmp(optarg, "T"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupMoveTop : DownloadQueue::eaFileMoveTop;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "B"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupMoveBottom : DownloadQueue::eaFileMoveBottom;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "P"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupPause : DownloadQueue::eaFilePause;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "A"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupPauseAllPars : DownloadQueue::eaFilePauseAllPars;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "R"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupPauseExtraPars : DownloadQueue::eaFilePauseExtraPars;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "U"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupResume : DownloadQueue::eaFileResume;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "D"))
|
||||
{
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupDelete : DownloadQueue::eaFileDelete;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "DP"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaGroupParkDelete;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "SF"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaGroupSortFiles;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K") || !strcasecmp(optarg, "CP"))
|
||||
{
|
||||
// switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions
|
||||
if (!group)
|
||||
{
|
||||
ReportError("Category can be set only for groups");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = !strcasecmp(optarg, "CP") ? DownloadQueue::eaGroupApplyCategory : DownloadQueue::eaGroupSetCategory;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (!strcasecmp(optarg, "N"))
|
||||
{
|
||||
if (!group)
|
||||
{
|
||||
ReportError("Only groups can be renamed");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = DownloadQueue::eaGroupSetName;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (!strcasecmp(optarg, "M"))
|
||||
{
|
||||
if (!group)
|
||||
{
|
||||
ReportError("Only groups can be merged");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = DownloadQueue::eaGroupMerge;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "S"))
|
||||
{
|
||||
m_editQueueAction = DownloadQueue::eaFileSplit;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
}
|
||||
else if (!strcasecmp(optarg, "O"))
|
||||
{
|
||||
if (!group)
|
||||
{
|
||||
ReportError("Post-process parameter can be set only for groups");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = DownloadQueue::eaGroupSetParameter;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
|
||||
if (!strchr(m_editQueueText, '='))
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!strcasecmp(optarg, "I"))
|
||||
{
|
||||
if (!group)
|
||||
{
|
||||
ReportError("Priority can be set only for groups");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = DownloadQueue::eaGroupSetPriority;
|
||||
|
||||
optind++;
|
||||
if (optind > argc)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueText = std::move(argv[optind-1]);
|
||||
|
||||
if (atoi(m_editQueueText) == 0 && strcmp("0", m_editQueueText))
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_editQueueOffset = atoi(optarg);
|
||||
if (m_editQueueOffset == 0)
|
||||
{
|
||||
ReportError("Could not parse value of option 'E'");
|
||||
return;
|
||||
}
|
||||
m_editQueueAction = group ? DownloadQueue::eaGroupMoveOffset : DownloadQueue::eaFileMoveOffset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Q':
|
||||
m_clientOperation = opClientRequestShutdown;
|
||||
break;
|
||||
case 'O':
|
||||
m_clientOperation = opClientRequestReload;
|
||||
break;
|
||||
case 'V':
|
||||
m_clientOperation = opClientRequestVersion;
|
||||
break;
|
||||
case 'W':
|
||||
m_clientOperation = opClientRequestWriteLog;
|
||||
if (!strcasecmp(optarg, "I")) {
|
||||
m_writeLogKind = (int)Message::mkInfo;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "W")) {
|
||||
m_writeLogKind = (int)Message::mkWarning;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "E")) {
|
||||
m_writeLogKind = (int)Message::mkError;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "D")) {
|
||||
m_writeLogKind = (int)Message::mkDetail;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "G")) {
|
||||
m_writeLogKind = (int)Message::mkDebug;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'W'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'K':
|
||||
// switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions
|
||||
m_addCategory = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
optind++;
|
||||
optarg = optind > argc ? nullptr : (char*)argv[optind-1];
|
||||
if (!optarg || !strncmp(optarg, "-", 1))
|
||||
{
|
||||
m_clientOperation = opClientRequestScanAsync;
|
||||
optind--;
|
||||
}
|
||||
else if (!strcasecmp(optarg, "W"))
|
||||
{
|
||||
m_clientOperation = opClientRequestScanSync;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Could not parse value of option 'S'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
m_errors = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_serverMode && m_clientOperation == opClientRequestDownloadPause)
|
||||
{
|
||||
m_pauseDownload = true;
|
||||
m_clientOperation = opClientNoOperation;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::PrintUsage(const char* com)
|
||||
{
|
||||
printf("Usage:\n"
|
||||
" %s [switches]\n\n"
|
||||
"Switches:\n"
|
||||
" -h, --help Print this help-message\n"
|
||||
" -v, --version Print version and exit\n"
|
||||
" -c, --configfile <file> Filename of configuration-file\n"
|
||||
" -n, --noconfigfile Prevent loading of configuration-file\n"
|
||||
" (required options must be passed with --option)\n"
|
||||
" -p, --printconfig Print configuration and exit\n"
|
||||
" -o, --option <name=value> Set or override option in configuration-file\n"
|
||||
" -s, --server Start nzbget as a server in console-mode\n"
|
||||
#ifndef WIN32
|
||||
" -D, --daemon Start nzbget as a server in daemon-mode\n"
|
||||
#endif
|
||||
" -V, --serverversion Print server's version and exit\n"
|
||||
" -Q, --quit Shutdown server\n"
|
||||
" -O, --reload Reload config and restart all services\n"
|
||||
" -A, --append [<options>] <nzb-file/url> Send file/url to server's\n"
|
||||
" download queue\n"
|
||||
" <options> are (multiple options must be separated with space):\n"
|
||||
" T Add file to the top (beginning) of queue\n"
|
||||
" P Pause added files\n"
|
||||
" C <name> Assign category to nzb-file\n"
|
||||
" N <name> Use this name as nzb-filename\n"
|
||||
" I <priority> Set priority (signed integer)\n"
|
||||
" DK <dupekey> Set duplicate key (string)\n"
|
||||
" DS <dupescore> Set duplicate score (signed integer)\n"
|
||||
" DM (score|all|force) Set duplicate mode\n"
|
||||
" -C, --connect Attach client to server\n"
|
||||
" -L, --list [F|FR|G|GR|O|H|S] [RegEx] Request list of items from server\n"
|
||||
" F List individual files and server status (default)\n"
|
||||
" FR Like \"F\" but apply regular expression filter\n"
|
||||
" G List groups (nzb-files) and server status\n"
|
||||
" GR Like \"G\" but apply regular expression filter\n"
|
||||
" O List post-processor-queue\n"
|
||||
" H List history\n"
|
||||
" HA List history, all records (incl. hidden)\n"
|
||||
" S Print only server status\n"
|
||||
" <RegEx> Regular expression (only with options \"FR\", \"GR\")\n"
|
||||
" using POSIX Extended Regular Expression Syntax\n"
|
||||
" -P, --pause [D|O|S] Pause server\n"
|
||||
" D Pause download queue (default)\n"
|
||||
" O Pause post-processor queue\n"
|
||||
" S Pause scan of incoming nzb-directory\n"
|
||||
" -U, --unpause [D|O|S] Unpause server\n"
|
||||
" D Unpause download queue (default)\n"
|
||||
" O Unpause post-processor queue\n"
|
||||
" S Unpause scan of incoming nzb-directory\n"
|
||||
" -R, --rate <speed> Set download rate on server, in KB/s\n"
|
||||
" -G, --log <lines> Request last <lines> lines from server's screen-log\n"
|
||||
" -W, --write <D|I|W|E|G> \"Text\" Send text to server's log\n"
|
||||
" -S, --scan [W] Scan incoming nzb-directory on server\n"
|
||||
" W Wait until scan completes (synchronous mode)\n"
|
||||
" -E, --edit [F|FN|FR|G|GN|GR|O|H] <action> <IDs/Names/RegExs> Edit items\n"
|
||||
" on server\n"
|
||||
" F Edit individual files (default)\n"
|
||||
" FN Like \"F\" but uses names (as \"group/file\")\n"
|
||||
" instead of IDs\n"
|
||||
" FR Like \"FN\" but with regular expressions\n"
|
||||
" G Edit all files in the group (same nzb-file)\n"
|
||||
" GN Like \"G\" but uses group names instead of IDs\n"
|
||||
" GR Like \"GN\" but with regular expressions\n"
|
||||
" O Edit post-processor-queue\n"
|
||||
" H Edit history\n"
|
||||
" <action> is one of:\n"
|
||||
" - for files (F) and groups (G):\n"
|
||||
" <+offset|-offset> Move in queue relative to current position,\n"
|
||||
" offset is an integer value\n"
|
||||
" T Move to top of queue\n"
|
||||
" B Move to bottom of queue\n"
|
||||
" D Delete\n"
|
||||
" P Pause\n"
|
||||
" U Resume (unpause)\n"
|
||||
" - for groups (G):\n"
|
||||
" A Pause all pars\n"
|
||||
" R Pause extra pars\n"
|
||||
" DP Delete but keep downloaded files\n"
|
||||
" I <priority> Set priority (signed integer)\n"
|
||||
" C <name> Set category\n"
|
||||
" CP <name> Set category and apply post-process parameters\n"
|
||||
" N <name> Rename\n"
|
||||
" M Merge\n"
|
||||
" SF Sort inner files for optimal order\n"
|
||||
" S <name> Split - create new group from selected files\n"
|
||||
" O <name>=<value> Set post-process parameter\n"
|
||||
" - for post-jobs (O):\n"
|
||||
" D Delete (cancel post-processing)\n"
|
||||
" - for history (H):\n"
|
||||
" D Delete\n"
|
||||
" P Post-process again\n"
|
||||
" R Download remaining files\n"
|
||||
" A Download again\n"
|
||||
" F Retry download of failed articles\n"
|
||||
" O <name>=<value> Set post-process parameter\n"
|
||||
" B Mark as bad\n"
|
||||
" G Mark as good\n"
|
||||
" S Mark as success\n"
|
||||
" <IDs> Comma-separated list of file- or group- ids or\n"
|
||||
" ranges of file-ids, e. g.: 1-5,3,10-22\n"
|
||||
" <Names> List of names (with options \"FN\" and \"GN\"),\n"
|
||||
" e. g.: \"my nzb download%cmyfile.nfo\" \"another nzb\"\n"
|
||||
" <RegExs> List of regular expressions (options \"FR\", \"GR\")\n"
|
||||
" using POSIX Extended Regular Expression Syntax\n",
|
||||
FileSystem::BaseFileName(com),
|
||||
PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
void CommandLineParser::InitFileArg(int argc, const char* argv[])
|
||||
{
|
||||
if (optind >= argc)
|
||||
{
|
||||
// no nzb-file passed
|
||||
if (!m_serverMode && !m_remoteClientMode &&
|
||||
(m_clientOperation == opClientNoOperation ||
|
||||
m_clientOperation == opClientRequestDownload ||
|
||||
m_clientOperation == opClientRequestWriteLog))
|
||||
{
|
||||
if (m_clientOperation == opClientRequestWriteLog)
|
||||
{
|
||||
ReportError("Log-text not specified");
|
||||
}
|
||||
else
|
||||
{
|
||||
ReportError("Nzb-file or Url not specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_clientOperation == opClientRequestEditQueue)
|
||||
{
|
||||
if (m_matchMode == mmId)
|
||||
{
|
||||
ParseFileIdList(argc, argv, optind);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseFileNameList(argc, argv, optind);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lastArg = argv[optind];
|
||||
|
||||
// Check if the file-name is a relative path or an absolute path
|
||||
// If the path starts with '/' its an absolute, else relative
|
||||
const char* fileName = argv[optind];
|
||||
|
||||
#ifdef WIN32
|
||||
m_argFilename = fileName;
|
||||
#else
|
||||
if (fileName[0] == '/' || !strncasecmp(fileName, "http://", 6) || !strncasecmp(fileName, "https://", 7))
|
||||
{
|
||||
m_argFilename = fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TEST
|
||||
m_argFilename.Reserve(1024 - 1);
|
||||
getcwd(m_argFilename, 1024);
|
||||
m_argFilename.AppendFmt("/%s", fileName);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_serverMode || m_remoteClientMode ||
|
||||
!(m_clientOperation == opClientNoOperation ||
|
||||
m_clientOperation == opClientRequestDownload ||
|
||||
m_clientOperation == opClientRequestWriteLog))
|
||||
{
|
||||
ReportError("Too many arguments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::ParseFileIdList(int argc, const char* argv[], int optind)
|
||||
{
|
||||
m_editQueueIdList.clear();
|
||||
|
||||
while (optind < argc)
|
||||
{
|
||||
CString writableFileIdList = argv[optind++];
|
||||
|
||||
char* optarg = strtok(writableFileIdList, ", ");
|
||||
while (optarg)
|
||||
{
|
||||
int editQueueIdFrom = 0;
|
||||
int editQueueIdTo = 0;
|
||||
const char* p = strchr(optarg, '-');
|
||||
if (p)
|
||||
{
|
||||
BString<100> buf;
|
||||
buf.Set(optarg, (int)(p - optarg));
|
||||
editQueueIdFrom = atoi(buf);
|
||||
editQueueIdTo = atoi(p + 1);
|
||||
if (editQueueIdFrom <= 0 || editQueueIdTo <= 0)
|
||||
{
|
||||
ReportError("invalid list of file IDs");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
editQueueIdFrom = atoi(optarg);
|
||||
if (editQueueIdFrom <= 0)
|
||||
{
|
||||
ReportError("invalid list of file IDs");
|
||||
return;
|
||||
}
|
||||
editQueueIdTo = editQueueIdFrom;
|
||||
}
|
||||
|
||||
int editQueueIdCount = 0;
|
||||
if (editQueueIdFrom < editQueueIdTo)
|
||||
{
|
||||
editQueueIdCount = editQueueIdTo - editQueueIdFrom + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
editQueueIdCount = editQueueIdFrom - editQueueIdTo + 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < editQueueIdCount; i++)
|
||||
{
|
||||
if (editQueueIdFrom < editQueueIdTo)
|
||||
{
|
||||
m_editQueueIdList.push_back(editQueueIdFrom + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_editQueueIdList.push_back(editQueueIdFrom - i);
|
||||
}
|
||||
}
|
||||
|
||||
optarg = strtok(nullptr, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::ParseFileNameList(int argc, const char* argv[], int optind)
|
||||
{
|
||||
while (optind < argc)
|
||||
{
|
||||
m_editQueueNameList.push_back(argv[optind++]);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineParser::ReportError(const char* errMessage)
|
||||
{
|
||||
m_errors = true;
|
||||
printf("%s\n", errMessage);
|
||||
}
|
||||
154
daemon/main/CommandLineParser.h
Normal file
154
daemon/main/CommandLineParser.h
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef COMMANDLINEPARSER_H
|
||||
#define COMMANDLINEPARSER_H
|
||||
|
||||
#include "NString.h"
|
||||
|
||||
class CommandLineParser
|
||||
{
|
||||
public:
|
||||
enum EClientOperation
|
||||
{
|
||||
opClientNoOperation,
|
||||
opClientRequestDownload,
|
||||
opClientRequestListFiles,
|
||||
opClientRequestListGroups,
|
||||
opClientRequestListStatus,
|
||||
opClientRequestSetRate,
|
||||
opClientRequestDumpDebug,
|
||||
opClientRequestEditQueue,
|
||||
opClientRequestLog,
|
||||
opClientRequestShutdown,
|
||||
opClientRequestReload,
|
||||
opClientRequestVersion,
|
||||
opClientRequestPostQueue,
|
||||
opClientRequestWriteLog,
|
||||
opClientRequestScanSync,
|
||||
opClientRequestScanAsync,
|
||||
opClientRequestDownloadPause,
|
||||
opClientRequestDownloadUnpause,
|
||||
opClientRequestPostPause,
|
||||
opClientRequestPostUnpause,
|
||||
opClientRequestScanPause,
|
||||
opClientRequestScanUnpause,
|
||||
opClientRequestHistory,
|
||||
opClientRequestHistoryAll
|
||||
};
|
||||
enum EMatchMode
|
||||
{
|
||||
mmId = 1,
|
||||
mmName,
|
||||
mmRegEx
|
||||
};
|
||||
|
||||
typedef std::vector<int> IdList;
|
||||
typedef std::vector<CString> NameList;
|
||||
|
||||
CommandLineParser(int argc, const char* argv[]);
|
||||
void PrintUsage(const char* com);
|
||||
bool GetErrors() { return m_errors; }
|
||||
bool GetNoConfig() { return m_noConfig; }
|
||||
const char* GetConfigFilename() { return m_configFilename; }
|
||||
bool GetServerMode() { return m_serverMode; }
|
||||
bool GetDaemonMode() { return m_daemonMode; }
|
||||
bool GetRemoteClientMode() { return m_remoteClientMode; }
|
||||
EClientOperation GetClientOperation() { return m_clientOperation; }
|
||||
NameList* GetOptionList() { return &m_optionList; }
|
||||
int GetEditQueueAction() { return m_editQueueAction; }
|
||||
int GetEditQueueOffset() { return m_editQueueOffset; }
|
||||
IdList* GetEditQueueIdList() { return &m_editQueueIdList; }
|
||||
NameList* GetEditQueueNameList() { return &m_editQueueNameList; }
|
||||
EMatchMode GetMatchMode() { return m_matchMode; }
|
||||
const char* GetEditQueueText() { return m_editQueueText; }
|
||||
const char* GetArgFilename() { return m_argFilename; }
|
||||
const char* GetAddCategory() { return m_addCategory; }
|
||||
bool GetAddPaused() { return m_addPaused; }
|
||||
const char* GetLastArg() { return m_lastArg; }
|
||||
int GetAddPriority() { return m_addPriority; }
|
||||
const char* GetAddNzbFilename() { return m_addNzbFilename; }
|
||||
bool GetAddTop() { return m_addTop; }
|
||||
const char* GetAddDupeKey() { return m_addDupeKey; }
|
||||
int GetAddDupeScore() { return m_addDupeScore; }
|
||||
int GetAddDupeMode() { return m_addDupeMode; }
|
||||
int GetSetRate() { return m_setRate; }
|
||||
int GetLogLines() { return m_logLines; }
|
||||
int GetWriteLogKind() { return m_writeLogKind; }
|
||||
bool GetTestBacktrace() { return m_testBacktrace; }
|
||||
bool GetWebGet() { return m_webGet; }
|
||||
const char* GetWebGetFilename() { return m_webGetFilename; }
|
||||
bool GetSigVerify() { return m_sigVerify; }
|
||||
const char* GetPubKeyFilename() { return m_pubKeyFilename; }
|
||||
const char* GetSigFilename() { return m_sigFilename; }
|
||||
bool GetPrintOptions() { return m_printOptions; }
|
||||
bool GetPrintVersion() { return m_printVersion; }
|
||||
bool GetPrintUsage() { return m_printUsage; }
|
||||
bool GetPauseDownload() const { return m_pauseDownload; }
|
||||
|
||||
private:
|
||||
bool m_noConfig = false;
|
||||
CString m_configFilename;
|
||||
|
||||
// Parsed command-line parameters
|
||||
bool m_errors = false;
|
||||
bool m_printVersion = false;
|
||||
bool m_printUsage = false;
|
||||
bool m_serverMode = false;
|
||||
bool m_daemonMode = false;
|
||||
bool m_remoteClientMode = false;
|
||||
EClientOperation m_clientOperation;
|
||||
NameList m_optionList;
|
||||
int m_editQueueAction = 0;
|
||||
int m_editQueueOffset = 0;
|
||||
IdList m_editQueueIdList;
|
||||
NameList m_editQueueNameList;
|
||||
EMatchMode m_matchMode = mmId;
|
||||
CString m_editQueueText;
|
||||
CString m_argFilename;
|
||||
CString m_addCategory;
|
||||
int m_addPriority = 0;
|
||||
bool m_addPaused = false;
|
||||
CString m_addNzbFilename;
|
||||
CString m_lastArg;
|
||||
bool m_printOptions = false;
|
||||
bool m_addTop = false;
|
||||
CString m_addDupeKey;
|
||||
int m_addDupeScore = 0;
|
||||
int m_addDupeMode = 0;
|
||||
int m_setRate = 0;
|
||||
int m_logLines = 0;
|
||||
int m_writeLogKind = 0;
|
||||
bool m_testBacktrace = false;
|
||||
bool m_webGet = false;
|
||||
CString m_webGetFilename;
|
||||
bool m_sigVerify = false;
|
||||
CString m_pubKeyFilename;
|
||||
CString m_sigFilename;
|
||||
bool m_pauseDownload = false;
|
||||
|
||||
void InitCommandLine(int argc, const char* argv[]);
|
||||
void InitFileArg(int argc, const char* argv[]);
|
||||
void ParseFileIdList(int argc, const char* argv[], int optind);
|
||||
void ParseFileNameList(int argc, const char* argv[], int optind);
|
||||
void ReportError(const char* errMessage);
|
||||
};
|
||||
|
||||
#endif
|
||||
123
daemon/main/DiskService.cpp
Normal file
123
daemon/main/DiskService.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2015-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "DiskService.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "StatMeter.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
DiskService::DiskService()
|
||||
{
|
||||
g_WorkState->Attach(this);
|
||||
}
|
||||
|
||||
void DiskService::Update(Subject* caller, void* aspect)
|
||||
{
|
||||
WakeUp();
|
||||
}
|
||||
|
||||
int DiskService::ServiceInterval()
|
||||
{
|
||||
return m_waitingRequiredDir ? 1 :
|
||||
g_Options->GetDiskSpace() <= 0 ? Service::Sleep :
|
||||
// notifications from 'WorkState' are not 100% reliable due to race conditions
|
||||
!g_WorkState->GetDownloading() ? 10 :
|
||||
1;
|
||||
}
|
||||
|
||||
void DiskService::ServiceWork()
|
||||
{
|
||||
debug("Disk service work");
|
||||
|
||||
if (g_Options->GetDiskSpace() > 0 && g_WorkState->GetDownloading())
|
||||
{
|
||||
// check free disk space every 1 second
|
||||
CheckDiskSpace();
|
||||
}
|
||||
|
||||
if (m_waitingRequiredDir)
|
||||
{
|
||||
CheckRequiredDir();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskService::CheckDiskSpace()
|
||||
{
|
||||
debug("Disk service work: check disk space");
|
||||
|
||||
int64 freeSpace = FileSystem::FreeDiskSize(g_Options->GetDestDir());
|
||||
if (freeSpace > -1 && freeSpace / 1024 / 1024 < g_Options->GetDiskSpace())
|
||||
{
|
||||
warn("Low disk space on %s. Pausing download", g_Options->GetDestDir());
|
||||
g_WorkState->SetPauseDownload(true);
|
||||
}
|
||||
|
||||
if (!Util::EmptyStr(g_Options->GetInterDir()))
|
||||
{
|
||||
freeSpace = FileSystem::FreeDiskSize(g_Options->GetInterDir());
|
||||
if (freeSpace > -1 && freeSpace / 1024 / 1024 < g_Options->GetDiskSpace())
|
||||
{
|
||||
warn("Low disk space on %s. Pausing download", g_Options->GetInterDir());
|
||||
g_WorkState->SetPauseDownload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiskService::CheckRequiredDir()
|
||||
{
|
||||
debug("Disk service work: check required dir");
|
||||
|
||||
if (!Util::EmptyStr(g_Options->GetRequiredDir()))
|
||||
{
|
||||
bool allExist = true;
|
||||
bool wasWaitingReported = m_waitingReported;
|
||||
// split RequiredDir into tokens
|
||||
Tokenizer tok(g_Options->GetRequiredDir(), ",;");
|
||||
while (const char* dir = tok.Next())
|
||||
{
|
||||
if (!FileSystem::FileExists(dir) && !FileSystem::DirectoryExists(dir))
|
||||
{
|
||||
if (!wasWaitingReported)
|
||||
{
|
||||
info("Waiting for required directory %s", dir);
|
||||
m_waitingReported = true;
|
||||
}
|
||||
allExist = false;
|
||||
}
|
||||
}
|
||||
if (!allExist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_waitingReported)
|
||||
{
|
||||
info("All required directories available");
|
||||
}
|
||||
|
||||
g_WorkState->SetTempPauseDownload(false);
|
||||
g_WorkState->SetTempPausePostprocess(false);
|
||||
m_waitingRequiredDir = false;
|
||||
}
|
||||
45
daemon/main/DiskService.h
Normal file
45
daemon/main/DiskService.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2015-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DISKSERVICE_H
|
||||
#define DISKSERVICE_H
|
||||
|
||||
#include "Service.h"
|
||||
#include "Observer.h"
|
||||
|
||||
class DiskService : public Service, public Observer
|
||||
{
|
||||
public:
|
||||
DiskService();
|
||||
|
||||
protected:
|
||||
virtual int ServiceInterval();
|
||||
virtual void ServiceWork();
|
||||
virtual void Update(Subject* caller, void* aspect);
|
||||
|
||||
private:
|
||||
bool m_waitingRequiredDir = true;
|
||||
bool m_waitingReported = false;
|
||||
|
||||
void CheckDiskSpace();
|
||||
void CheckRequiredDir();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,188 +14,160 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Maintenance.h"
|
||||
#include "Options.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
extern Maintenance* g_pMaintenance;
|
||||
extern void ExitProc();
|
||||
extern int g_ArgumentCount;
|
||||
extern char* (*g_Arguments)[];
|
||||
|
||||
Maintenance::Maintenance()
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
class Signature
|
||||
{
|
||||
m_iIDMessageGen = 0;
|
||||
m_UpdateScriptController = NULL;
|
||||
m_szUpdateScript = NULL;
|
||||
}
|
||||
public:
|
||||
Signature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename);
|
||||
~Signature();
|
||||
bool Verify();
|
||||
|
||||
private:
|
||||
const char* m_inFilename;
|
||||
const char* m_sigFilename;
|
||||
const char* m_pubKeyFilename;
|
||||
uchar m_inHash[SHA256_DIGEST_LENGTH];
|
||||
uchar m_signature[256];
|
||||
RSA* m_pubKey;
|
||||
|
||||
bool ReadSignature();
|
||||
bool ComputeInHash();
|
||||
bool ReadPubKey();
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
Maintenance::~Maintenance()
|
||||
{
|
||||
m_mutexController.Lock();
|
||||
if (m_UpdateScriptController)
|
||||
bool waitScript = false;
|
||||
{
|
||||
m_UpdateScriptController->Detach();
|
||||
m_mutexController.Unlock();
|
||||
while (m_UpdateScriptController)
|
||||
Guard guard(m_controllerMutex);
|
||||
if (m_updateScriptController)
|
||||
{
|
||||
usleep(20*1000);
|
||||
m_updateScriptController->Detach();
|
||||
waitScript = true;
|
||||
}
|
||||
}
|
||||
|
||||
ClearMessages();
|
||||
|
||||
free(m_szUpdateScript);
|
||||
if (waitScript)
|
||||
{
|
||||
while (m_updateScriptController)
|
||||
{
|
||||
Util::Sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Maintenance::ResetUpdateController()
|
||||
{
|
||||
m_mutexController.Lock();
|
||||
m_UpdateScriptController = NULL;
|
||||
m_mutexController.Unlock();
|
||||
Guard guard(m_controllerMutex);
|
||||
m_updateScriptController = nullptr;
|
||||
}
|
||||
|
||||
void Maintenance::ClearMessages()
|
||||
void Maintenance::AddMessage(Message::EKind kind, time_t time, const char * text)
|
||||
{
|
||||
for (Log::Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++)
|
||||
if (time == 0)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
m_Messages.clear();
|
||||
}
|
||||
|
||||
Log::Messages* Maintenance::LockMessages()
|
||||
{
|
||||
m_mutexLog.Lock();
|
||||
return &m_Messages;
|
||||
}
|
||||
|
||||
void Maintenance::UnlockMessages()
|
||||
{
|
||||
m_mutexLog.Unlock();
|
||||
}
|
||||
|
||||
void Maintenance::AppendMessage(Message::EKind eKind, time_t tTime, const char * szText)
|
||||
{
|
||||
if (tTime == 0)
|
||||
{
|
||||
tTime = time(NULL);
|
||||
time = Util::CurrentTime();
|
||||
}
|
||||
|
||||
m_mutexLog.Lock();
|
||||
Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText);
|
||||
m_Messages.push_back(pMessage);
|
||||
m_mutexLog.Unlock();
|
||||
Guard guard(m_logMutex);
|
||||
m_messages.emplace_back(++m_idMessageGen, kind, time, text);
|
||||
}
|
||||
|
||||
bool Maintenance::StartUpdate(EBranch eBranch)
|
||||
bool Maintenance::StartUpdate(EBranch branch)
|
||||
{
|
||||
m_mutexController.Lock();
|
||||
bool bAlreadyUpdating = m_UpdateScriptController != NULL;
|
||||
m_mutexController.Unlock();
|
||||
bool alreadyUpdating;
|
||||
{
|
||||
Guard guard(m_controllerMutex);
|
||||
alreadyUpdating = m_updateScriptController != nullptr;
|
||||
}
|
||||
|
||||
if (bAlreadyUpdating)
|
||||
if (alreadyUpdating)
|
||||
{
|
||||
error("Could not start update-script: update-script is already running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_szUpdateScript)
|
||||
{
|
||||
free(m_szUpdateScript);
|
||||
m_szUpdateScript = NULL;
|
||||
}
|
||||
|
||||
if (!ReadPackageInfoStr("install-script", &m_szUpdateScript))
|
||||
if (!ReadPackageInfoStr("install-script", m_updateScript))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearMessages();
|
||||
// make absolute path
|
||||
if (m_updateScript[0] != PATH_SEPARATOR
|
||||
#ifdef WIN32
|
||||
&& !(strlen(m_updateScript) > 2 && m_updateScript[1] == ':')
|
||||
#endif
|
||||
)
|
||||
{
|
||||
m_updateScript = CString::FormatStr("%s%c%s", g_Options->GetAppDir(), PATH_SEPARATOR, *m_updateScript);
|
||||
}
|
||||
|
||||
m_UpdateScriptController = new UpdateScriptController();
|
||||
m_UpdateScriptController->SetScript(m_szUpdateScript);
|
||||
m_UpdateScriptController->SetBranch(eBranch);
|
||||
m_UpdateScriptController->SetAutoDestroy(true);
|
||||
m_messages.clear();
|
||||
|
||||
m_UpdateScriptController->Start();
|
||||
m_updateScriptController = new UpdateScriptController();
|
||||
m_updateScriptController->SetArgs({*m_updateScript});
|
||||
m_updateScriptController->SetBranch(branch);
|
||||
m_updateScriptController->SetAutoDestroy(true);
|
||||
|
||||
m_updateScriptController->Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Maintenance::CheckUpdates(char** pUpdateInfo)
|
||||
bool Maintenance::CheckUpdates(CString& updateInfo)
|
||||
{
|
||||
char* szUpdateInfoScript;
|
||||
if (!ReadPackageInfoStr("update-info-script", &szUpdateInfoScript))
|
||||
CString updateInfoScript;
|
||||
if (!ReadPackageInfoStr("update-info-script", updateInfoScript))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*pUpdateInfo = NULL;
|
||||
UpdateInfoScriptController::ExecuteScript(szUpdateInfoScript, pUpdateInfo);
|
||||
UpdateInfoScriptController::ExecuteScript(updateInfoScript, updateInfo);
|
||||
|
||||
free(szUpdateInfoScript);
|
||||
|
||||
return *pUpdateInfo;
|
||||
return updateInfo.Length() > 0;
|
||||
}
|
||||
|
||||
bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue)
|
||||
bool Maintenance::ReadPackageInfoStr(const char* key, CString& value)
|
||||
{
|
||||
char szFileName[1024];
|
||||
snprintf(szFileName, 1024, "%s%cpackage-info.json", g_pOptions->GetWebDir(), PATH_SEPARATOR);
|
||||
szFileName[1024-1] = '\0';
|
||||
BString<1024> fileName("%s%cpackage-info.json", g_Options->GetWebDir(), PATH_SEPARATOR);
|
||||
|
||||
char* szPackageInfo;
|
||||
int iPackageInfoLen;
|
||||
if (!Util::LoadFileIntoBuffer(szFileName, &szPackageInfo, &iPackageInfoLen))
|
||||
CharBuffer packageInfo;
|
||||
if (!FileSystem::LoadFileIntoBuffer(fileName, packageInfo, true))
|
||||
{
|
||||
error("Could not load file %s", szFileName);
|
||||
error("Could not load file %s", *fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
char szKeyStr[100];
|
||||
snprintf(szKeyStr, 100, "\"%s\"", szKey);
|
||||
szKeyStr[100-1] = '\0';
|
||||
BString<100> keyStr("\"%s\"", key);
|
||||
|
||||
char* p = strstr(szPackageInfo, szKeyStr);
|
||||
char* p = strstr(packageInfo, keyStr);
|
||||
if (!p)
|
||||
{
|
||||
error("Could not parse file %s", szFileName);
|
||||
free(szPackageInfo);
|
||||
error("Could not parse file %s", *fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
p = strchr(p + strlen(szKeyStr), '"');
|
||||
p = strchr(p + strlen(keyStr), '"');
|
||||
if (!p)
|
||||
{
|
||||
error("Could not parse file %s", szFileName);
|
||||
free(szPackageInfo);
|
||||
error("Could not parse file %s", *fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -203,123 +175,242 @@ bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue)
|
||||
char* pend = strchr(p, '"');
|
||||
if (!pend)
|
||||
{
|
||||
error("Could not parse file %s", szFileName);
|
||||
free(szPackageInfo);
|
||||
error("Could not parse file %s", *fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
int iLen = pend - p;
|
||||
if (iLen >= sizeof(szFileName))
|
||||
size_t len = pend - p;
|
||||
if (len >= sizeof(fileName))
|
||||
{
|
||||
error("Could not parse file %s", szFileName);
|
||||
free(szPackageInfo);
|
||||
error("Could not parse file %s", *fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
*pValue = (char*)malloc(iLen+1);
|
||||
strncpy(*pValue, p, iLen);
|
||||
(*pValue)[iLen] = '\0';
|
||||
value.Reserve(len);
|
||||
strncpy(value, p, len);
|
||||
value[len] = '\0';
|
||||
|
||||
WebUtil::JsonDecode(*pValue);
|
||||
|
||||
free(szPackageInfo);
|
||||
WebUtil::JsonDecode(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Maintenance::VerifySignature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename)
|
||||
{
|
||||
#ifdef HAVE_OPENSSL
|
||||
Signature signature(inFilename, sigFilename, pubKeyFilename);
|
||||
return signature.Verify();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UpdateScriptController::Run()
|
||||
{
|
||||
m_iPrefixLen = 0;
|
||||
// the update-script should not be automatically terminated when the program quits
|
||||
UnregisterRunningScript();
|
||||
|
||||
m_prefixLen = 0;
|
||||
PrintMessage(Message::mkInfo, "Executing update-script %s", GetScript());
|
||||
|
||||
char szInfoName[1024];
|
||||
snprintf(szInfoName, 1024, "update-script %s", Util::BaseFileName(GetScript()));
|
||||
szInfoName[1024-1] = '\0';
|
||||
SetInfoName(szInfoName);
|
||||
BString<1024> infoName("update-script %s", FileSystem::BaseFileName(GetScript()));
|
||||
SetInfoName(infoName);
|
||||
|
||||
const char* szBranchName[] = { "STABLE", "TESTING", "DEVEL" };
|
||||
SetEnvVar("NZBUP_BRANCH", szBranchName[m_eBranch]);
|
||||
const char* branchName[] = { "STABLE", "TESTING", "DEVEL" };
|
||||
SetEnvVar("NZBUP_BRANCH", branchName[m_branch]);
|
||||
|
||||
SetEnvVar("NZBUP_RUNMODE", g_Options->GetDaemonMode() ? "DAEMON" : "SERVER");
|
||||
|
||||
for (int i = 0; i < g_ArgumentCount; i++)
|
||||
{
|
||||
BString<100> envName("NZBUP_CMDLINE%i", i);
|
||||
SetEnvVar(envName, (*g_Arguments)[i]);
|
||||
}
|
||||
|
||||
char szProcessID[20];
|
||||
#ifdef WIN32
|
||||
int pid = (int)GetCurrentProcessId();
|
||||
#else
|
||||
int pid = (int)getppid();
|
||||
int pid = (int)getpid();
|
||||
#endif
|
||||
snprintf(szProcessID, 20, "%i", pid);
|
||||
szProcessID[20-1] = '\0';
|
||||
SetEnvVar("NZBUP_PROCESSID", szProcessID);
|
||||
|
||||
char szLogPrefix[100];
|
||||
strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 100);
|
||||
szLogPrefix[100-1] = '\0';
|
||||
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
SetLogPrefix(szLogPrefix);
|
||||
m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
|
||||
SetEnvVar("NZBUP_PROCESSID", BString<100>("%i", pid));
|
||||
|
||||
BString<100> logPrefix = FileSystem::BaseFileName(GetScript());
|
||||
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
SetLogPrefix(logPrefix);
|
||||
m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": ");
|
||||
|
||||
Execute();
|
||||
|
||||
g_pMaintenance->ResetUpdateController();
|
||||
g_Maintenance->ResetUpdateController();
|
||||
}
|
||||
|
||||
void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText)
|
||||
void UpdateScriptController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
szText = szText + m_iPrefixLen;
|
||||
text = text + m_prefixLen;
|
||||
|
||||
g_pMaintenance->AppendMessage(eKind, time(NULL), szText);
|
||||
ScriptController::AddMessage(eKind, szText);
|
||||
}
|
||||
|
||||
void UpdateInfoScriptController::ExecuteScript(const char* szScript, char** pUpdateInfo)
|
||||
{
|
||||
detail("Executing update-info-script %s", Util::BaseFileName(szScript));
|
||||
|
||||
UpdateInfoScriptController* pScriptController = new UpdateInfoScriptController();
|
||||
pScriptController->SetScript(szScript);
|
||||
|
||||
char szInfoName[1024];
|
||||
snprintf(szInfoName, 1024, "update-info-script %s", Util::BaseFileName(szScript));
|
||||
szInfoName[1024-1] = '\0';
|
||||
pScriptController->SetInfoName(szInfoName);
|
||||
|
||||
char szLogPrefix[1024];
|
||||
strncpy(szLogPrefix, Util::BaseFileName(szScript), 1024);
|
||||
szLogPrefix[1024-1] = '\0';
|
||||
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
pScriptController->SetLogPrefix(szLogPrefix);
|
||||
pScriptController->m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
|
||||
|
||||
pScriptController->Execute();
|
||||
|
||||
if (pScriptController->m_UpdateInfo.GetBuffer())
|
||||
if (!strncmp(text, "[NZB] ", 6))
|
||||
{
|
||||
int iLen = strlen(pScriptController->m_UpdateInfo.GetBuffer());
|
||||
*pUpdateInfo = (char*)malloc(iLen + 1);
|
||||
strncpy(*pUpdateInfo, pScriptController->m_UpdateInfo.GetBuffer(), iLen);
|
||||
(*pUpdateInfo)[iLen] = '\0';
|
||||
}
|
||||
|
||||
delete pScriptController;
|
||||
}
|
||||
|
||||
void UpdateInfoScriptController::AddMessage(Message::EKind eKind, const char* szText)
|
||||
{
|
||||
szText = szText + m_iPrefixLen;
|
||||
|
||||
if (!strncmp(szText, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", szText + 6);
|
||||
if (!strncmp(szText + 6, "[UPDATEINFO]", 12))
|
||||
debug("Command %s detected", text + 6);
|
||||
if (!strcmp(text + 6, "QUIT"))
|
||||
{
|
||||
m_UpdateInfo.Append(szText + 6 + 12);
|
||||
Detach();
|
||||
ExitProc();
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", szText, GetInfoName());
|
||||
error("Invalid command \"%s\" received", text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptController::AddMessage(eKind, szText);
|
||||
g_Maintenance->AddMessage(kind, Util::CurrentTime(), text);
|
||||
ScriptController::AddMessage(kind, text);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInfoScriptController::ExecuteScript(const char* script, CString& updateInfo)
|
||||
{
|
||||
detail("Executing update-info-script %s", FileSystem::BaseFileName(script));
|
||||
|
||||
UpdateInfoScriptController scriptController;
|
||||
scriptController.SetArgs({script});
|
||||
|
||||
BString<1024> infoName("update-info-script %s", FileSystem::BaseFileName(script));
|
||||
scriptController.SetInfoName(infoName);
|
||||
|
||||
BString<1024> logPrefix = FileSystem::BaseFileName(script);
|
||||
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
scriptController.SetLogPrefix(logPrefix);
|
||||
scriptController.m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": ");
|
||||
|
||||
scriptController.Execute();
|
||||
|
||||
if (!scriptController.m_updateInfo.Empty())
|
||||
{
|
||||
updateInfo = scriptController.m_updateInfo;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInfoScriptController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
text = text + m_prefixLen;
|
||||
|
||||
if (!strncmp(text, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", text + 6);
|
||||
if (!strncmp(text + 6, "[UPDATEINFO]", 12))
|
||||
{
|
||||
m_updateInfo.Append(text + 6 + 12);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", text, GetInfoName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptController::AddMessage(kind, text);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
Signature::Signature(const char *inFilename, const char *sigFilename, const char *pubKeyFilename)
|
||||
{
|
||||
m_inFilename = inFilename;
|
||||
m_sigFilename = sigFilename;
|
||||
m_pubKeyFilename = pubKeyFilename;
|
||||
m_pubKey = nullptr;
|
||||
}
|
||||
|
||||
Signature::~Signature()
|
||||
{
|
||||
RSA_free(m_pubKey);
|
||||
}
|
||||
|
||||
// Calculate SHA-256 for input file (m_inFilename)
|
||||
bool Signature::ComputeInHash()
|
||||
{
|
||||
DiskFile infile;
|
||||
if (!infile.Open(m_inFilename, DiskFile::omRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
CharBuffer buffer(32*1024);
|
||||
while(int bytesRead = (int)infile.Read(buffer, buffer.Size()))
|
||||
{
|
||||
SHA256_Update(&sha256, buffer, bytesRead);
|
||||
}
|
||||
SHA256_Final(m_inHash, &sha256);
|
||||
infile.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read signature from file (m_sigFilename) into memory
|
||||
bool Signature::ReadSignature()
|
||||
{
|
||||
BString<1024> sigTitle("\"RSA-SHA256(%s)\" : \"", FileSystem::BaseFileName(m_inFilename));
|
||||
|
||||
DiskFile infile;
|
||||
if (!infile.Open(m_sigFilename, DiskFile::omRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
int titLen = strlen(sigTitle);
|
||||
char buf[1024];
|
||||
uchar* output = m_signature;
|
||||
while (infile.ReadLine(buf, sizeof(buf) - 1))
|
||||
{
|
||||
if (!strncmp(buf, sigTitle, titLen))
|
||||
{
|
||||
char* hexSig = buf + titLen;
|
||||
int sigLen = strlen(hexSig);
|
||||
if (sigLen > 2)
|
||||
{
|
||||
hexSig[sigLen - 2] = '\0'; // trim trailing ",
|
||||
}
|
||||
while (*hexSig && *(hexSig+1) && output != m_signature + sizeof(m_signature))
|
||||
{
|
||||
uchar c1 = *hexSig++;
|
||||
uchar c2 = *hexSig++;
|
||||
c1 = '0' <= c1 && c1 <= '9' ? c1 - '0' : 'A' <= c1 && c1 <= 'F' ? c1 - 'A' + 10 :
|
||||
'a' <= c1 && c1 <= 'f' ? c1 - 'a' + 10 : 0;
|
||||
c2 = '0' <= c2 && c2 <= '9' ? c2 - '0' : 'A' <= c2 && c2 <= 'F' ? c2 - 'A' + 10 :
|
||||
'a' <= c2 && c2 <= 'f' ? c2 - 'a' + 10 : 0;
|
||||
uchar ch = (c1 << 4) + c2;
|
||||
*output++ = (char)ch;
|
||||
}
|
||||
ok = output == m_signature + sizeof(m_signature);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
infile.Close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Read public key from file (m_szPubKeyFilename) into memory
|
||||
bool Signature::ReadPubKey()
|
||||
{
|
||||
CharBuffer keybuf;
|
||||
if (!FileSystem::LoadFileIntoBuffer(m_pubKeyFilename, keybuf, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BIO* mem = BIO_new_mem_buf(keybuf, keybuf.Size());
|
||||
m_pubKey = PEM_read_bio_RSA_PUBKEY(mem, nullptr, nullptr, nullptr);
|
||||
BIO_free(mem);
|
||||
return m_pubKey != nullptr;
|
||||
}
|
||||
|
||||
bool Signature::Verify()
|
||||
{
|
||||
return ComputeInHash() && ReadSignature() && ReadPubKey() &&
|
||||
RSA_verify(NID_sha256, m_inHash, sizeof(m_inHash), m_signature, sizeof(m_signature), m_pubKey) == 1;
|
||||
}
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,17 +14,13 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MAINTENANCE_H
|
||||
#define MAINTENANCE_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Thread.h"
|
||||
#include "Script.h"
|
||||
#include "Log.h"
|
||||
@@ -34,16 +30,6 @@ class UpdateScriptController;
|
||||
|
||||
class Maintenance
|
||||
{
|
||||
private:
|
||||
Log::Messages m_Messages;
|
||||
Mutex m_mutexLog;
|
||||
Mutex m_mutexController;
|
||||
int m_iIDMessageGen;
|
||||
UpdateScriptController* m_UpdateScriptController;
|
||||
char* m_szUpdateScript;
|
||||
|
||||
bool ReadPackageInfoStr(const char* szKey, char** pValue);
|
||||
|
||||
public:
|
||||
enum EBranch
|
||||
{
|
||||
@@ -52,42 +38,52 @@ public:
|
||||
brDevel
|
||||
};
|
||||
|
||||
Maintenance();
|
||||
~Maintenance();
|
||||
void ClearMessages();
|
||||
void AppendMessage(Message::EKind eKind, time_t tTime, const char* szText);
|
||||
Log::Messages* LockMessages();
|
||||
void UnlockMessages();
|
||||
bool StartUpdate(EBranch eBranch);
|
||||
void ResetUpdateController();
|
||||
bool CheckUpdates(char** pUpdateInfo);
|
||||
~Maintenance();
|
||||
void AddMessage(Message::EKind kind, time_t time, const char* text);
|
||||
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
|
||||
bool StartUpdate(EBranch branch);
|
||||
void ResetUpdateController();
|
||||
bool CheckUpdates(CString& updateInfo);
|
||||
static bool VerifySignature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename);
|
||||
|
||||
private:
|
||||
MessageList m_messages;
|
||||
Mutex m_logMutex;
|
||||
Mutex m_controllerMutex;
|
||||
int m_idMessageGen = 0;
|
||||
UpdateScriptController* m_updateScriptController = nullptr;
|
||||
CString m_updateScript;
|
||||
|
||||
bool ReadPackageInfoStr(const char* key, CString& value);
|
||||
};
|
||||
|
||||
extern Maintenance* g_Maintenance;
|
||||
|
||||
class UpdateScriptController : public Thread, public ScriptController
|
||||
{
|
||||
private:
|
||||
Maintenance::EBranch m_eBranch;
|
||||
int m_iPrefixLen;
|
||||
public:
|
||||
virtual void Run();
|
||||
void SetBranch(Maintenance::EBranch branch) { m_branch = branch; }
|
||||
|
||||
protected:
|
||||
virtual void AddMessage(Message::EKind eKind, const char* szText);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
public:
|
||||
virtual void Run();
|
||||
void SetBranch(Maintenance::EBranch eBranch) { m_eBranch = eBranch; }
|
||||
private:
|
||||
Maintenance::EBranch m_branch;
|
||||
int m_prefixLen;
|
||||
};
|
||||
|
||||
class UpdateInfoScriptController : public ScriptController
|
||||
{
|
||||
public:
|
||||
static void ExecuteScript(const char* script, CString& updateInfo);
|
||||
|
||||
private:
|
||||
int m_iPrefixLen;
|
||||
StringBuilder m_UpdateInfo;
|
||||
int m_prefixLen;
|
||||
StringBuilder m_updateInfo;
|
||||
|
||||
protected:
|
||||
virtual void AddMessage(Message::EKind eKind, const char* szText);
|
||||
|
||||
public:
|
||||
static void ExecuteScript(const char* szScript, char** pUpdateInfo);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,55 +15,20 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef OPTIONS_H
|
||||
#define OPTIONS_H
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Thread.h"
|
||||
#include "Util.h"
|
||||
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
enum EClientOperation
|
||||
{
|
||||
opClientNoOperation,
|
||||
opClientRequestDownload,
|
||||
opClientRequestListFiles,
|
||||
opClientRequestListGroups,
|
||||
opClientRequestListStatus,
|
||||
opClientRequestSetRate,
|
||||
opClientRequestDumpDebug,
|
||||
opClientRequestEditQueue,
|
||||
opClientRequestLog,
|
||||
opClientRequestShutdown,
|
||||
opClientRequestReload,
|
||||
opClientRequestVersion,
|
||||
opClientRequestPostQueue,
|
||||
opClientRequestWriteLog,
|
||||
opClientRequestScanSync,
|
||||
opClientRequestScanAsync,
|
||||
opClientRequestDownloadPause,
|
||||
opClientRequestDownloadUnpause,
|
||||
opClientRequestPostPause,
|
||||
opClientRequestPostUnpause,
|
||||
opClientRequestScanPause,
|
||||
opClientRequestScanUnpause,
|
||||
opClientRequestHistory,
|
||||
opClientRequestDownloadUrl
|
||||
};
|
||||
enum EWriteLog
|
||||
{
|
||||
wlNone,
|
||||
@@ -94,468 +59,418 @@ public:
|
||||
enum EParScan
|
||||
{
|
||||
psLimited,
|
||||
psExtended,
|
||||
psFull,
|
||||
psAuto
|
||||
psDupe
|
||||
};
|
||||
enum EHealthCheck
|
||||
{
|
||||
hcPause,
|
||||
hcDelete,
|
||||
hcPark,
|
||||
hcNone
|
||||
};
|
||||
enum EMatchMode
|
||||
enum ESchedulerCommand
|
||||
{
|
||||
mmID = 1,
|
||||
mmName,
|
||||
mmRegEx
|
||||
scPauseDownload,
|
||||
scUnpauseDownload,
|
||||
scPausePostProcess,
|
||||
scUnpausePostProcess,
|
||||
scDownloadRate,
|
||||
scScript,
|
||||
scProcess,
|
||||
scPauseScan,
|
||||
scUnpauseScan,
|
||||
scActivateServer,
|
||||
scDeactivateServer,
|
||||
scFetchFeed
|
||||
};
|
||||
enum EPostStrategy
|
||||
{
|
||||
ppSequential,
|
||||
ppBalanced,
|
||||
ppAggressive,
|
||||
ppRocket
|
||||
};
|
||||
enum EFileNaming
|
||||
{
|
||||
nfAuto,
|
||||
nfArticle,
|
||||
nfNzb
|
||||
};
|
||||
|
||||
class OptEntry
|
||||
{
|
||||
private:
|
||||
char* m_szName;
|
||||
char* m_szValue;
|
||||
char* m_szDefValue;
|
||||
int m_iLineNo;
|
||||
public:
|
||||
OptEntry(const char* name, const char* value) :
|
||||
m_name(name), m_value(value) {}
|
||||
void SetName(const char* name) { m_name = name; }
|
||||
const char* GetName() { return m_name; }
|
||||
void SetValue(const char* value);
|
||||
const char* GetValue() { return m_value; }
|
||||
const char* GetDefValue() { return m_defValue; }
|
||||
int GetLineNo() { return m_lineNo; }
|
||||
bool Restricted();
|
||||
|
||||
void SetName(const char* szName);
|
||||
void SetValue(const char* szValue);
|
||||
void SetLineNo(int iLineNo) { m_iLineNo = iLineNo; }
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_value;
|
||||
CString m_defValue;
|
||||
int m_lineNo = 0;
|
||||
|
||||
void SetLineNo(int lineNo) { m_lineNo = lineNo; }
|
||||
|
||||
friend class Options;
|
||||
|
||||
public:
|
||||
OptEntry();
|
||||
OptEntry(const char* szName, const char* szValue);
|
||||
~OptEntry();
|
||||
const char* GetName() { return m_szName; }
|
||||
const char* GetValue() { return m_szValue; }
|
||||
const char* GetDefValue() { return m_szDefValue; }
|
||||
int GetLineNo() { return m_iLineNo; }
|
||||
};
|
||||
|
||||
typedef std::vector<OptEntry*> OptEntriesBase;
|
||||
|
||||
typedef std::deque<OptEntry> OptEntriesBase;
|
||||
|
||||
class OptEntries: public OptEntriesBase
|
||||
{
|
||||
public:
|
||||
~OptEntries();
|
||||
OptEntry* FindOption(const char* szName);
|
||||
OptEntry* FindOption(const char* name);
|
||||
};
|
||||
|
||||
typedef std::vector<char*> NameList;
|
||||
typedef GuardedPtr<OptEntries> GuardedOptEntries;
|
||||
|
||||
typedef std::vector<CString> NameList;
|
||||
typedef std::vector<const char*> CmdOptList;
|
||||
|
||||
class Category
|
||||
{
|
||||
private:
|
||||
char* m_szName;
|
||||
char* m_szDestDir;
|
||||
bool m_bUnpack;
|
||||
char* m_szPostScript;
|
||||
NameList m_Aliases;
|
||||
|
||||
public:
|
||||
Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szPostScript);
|
||||
~Category();
|
||||
const char* GetName() { return m_szName; }
|
||||
const char* GetDestDir() { return m_szDestDir; }
|
||||
bool GetUnpack() { return m_bUnpack; }
|
||||
const char* GetPostScript() { return m_szPostScript; }
|
||||
NameList* GetAliases() { return &m_Aliases; }
|
||||
Category(const char* name, const char* destDir, bool unpack, const char* extensions) :
|
||||
m_name(name), m_destDir(destDir), m_unpack(unpack), m_extensions(extensions) {}
|
||||
const char* GetName() { return m_name; }
|
||||
const char* GetDestDir() { return m_destDir; }
|
||||
bool GetUnpack() { return m_unpack; }
|
||||
const char* GetExtensions() { return m_extensions; }
|
||||
NameList* GetAliases() { return &m_aliases; }
|
||||
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_destDir;
|
||||
bool m_unpack;
|
||||
CString m_extensions;
|
||||
NameList m_aliases;
|
||||
};
|
||||
|
||||
typedef std::vector<Category*> CategoriesBase;
|
||||
|
||||
typedef std::deque<Category> CategoriesBase;
|
||||
|
||||
class Categories: public CategoriesBase
|
||||
{
|
||||
public:
|
||||
~Categories();
|
||||
Category* FindCategory(const char* szName, bool bSearchAliases);
|
||||
Category* FindCategory(const char* name, bool searchAliases);
|
||||
};
|
||||
|
||||
class Script
|
||||
{
|
||||
private:
|
||||
char* m_szName;
|
||||
char* m_szLocation;
|
||||
char* m_szDisplayName;
|
||||
bool m_bPostScript;
|
||||
bool m_bScanScript;
|
||||
bool m_bQueueScript;
|
||||
bool m_bSchedulerScript;
|
||||
char* m_szQueueEvents;
|
||||
|
||||
public:
|
||||
Script(const char* szName, const char* szLocation);
|
||||
~Script();
|
||||
const char* GetName() { return m_szName; }
|
||||
const char* GetLocation() { return m_szLocation; }
|
||||
void SetDisplayName(const char* szDisplayName);
|
||||
const char* GetDisplayName() { return m_szDisplayName; }
|
||||
bool GetPostScript() { return m_bPostScript; }
|
||||
void SetPostScript(bool bPostScript) { m_bPostScript = bPostScript; }
|
||||
bool GetScanScript() { return m_bScanScript; }
|
||||
void SetScanScript(bool bScanScript) { m_bScanScript = bScanScript; }
|
||||
bool GetQueueScript() { return m_bQueueScript; }
|
||||
void SetQueueScript(bool bQueueScript) { m_bQueueScript = bQueueScript; }
|
||||
bool GetSchedulerScript() { return m_bSchedulerScript; }
|
||||
void SetSchedulerScript(bool bSchedulerScript) { m_bSchedulerScript = bSchedulerScript; }
|
||||
void SetQueueEvents(const char* szQueueEvents);
|
||||
const char* GetQueueEvents() { return m_szQueueEvents; }
|
||||
};
|
||||
|
||||
typedef std::list<Script*> ScriptsBase;
|
||||
|
||||
class Scripts: public ScriptsBase
|
||||
class Extender
|
||||
{
|
||||
public:
|
||||
~Scripts();
|
||||
void Clear();
|
||||
Script* Find(const char* szName);
|
||||
virtual void AddNewsServer(int id, bool active, const char* name, const char* host,
|
||||
int port, int ipVersion, const char* user, const char* pass, bool joinGroup,
|
||||
bool tls, const char* cipher, int maxConnections, int retention,
|
||||
int level, int group, bool optional) = 0;
|
||||
virtual void AddFeed(int id, const char* name, const char* url, int interval,
|
||||
const char* filter, bool backlog, bool pauseNzb, const char* category,
|
||||
int priority, const char* extensions) {}
|
||||
virtual void AddTask(int id, int hours, int minutes, int weekDaysBits, ESchedulerCommand command,
|
||||
const char* param) {}
|
||||
virtual void SetupFirstStart() {}
|
||||
};
|
||||
|
||||
class ConfigTemplate
|
||||
{
|
||||
private:
|
||||
Script* m_pScript;
|
||||
char* m_szTemplate;
|
||||
Options(const char* exeName, const char* configFilename, bool noConfig,
|
||||
CmdOptList* commandLineOptions, Extender* extender);
|
||||
Options(CmdOptList* commandLineOptions, Extender* extender);
|
||||
~Options();
|
||||
|
||||
friend class Options;
|
||||
static bool SplitOptionString(const char* option, CString& optName, CString& optValue);
|
||||
static void ConvertOldOptions(OptEntries* optEntries);
|
||||
bool GetFatalError() { return m_fatalError; }
|
||||
GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); }
|
||||
void CreateSchedulerTask(int id, const char* time, const char* weekDays,
|
||||
ESchedulerCommand command, const char* param);
|
||||
|
||||
public:
|
||||
ConfigTemplate(Script* pScript, const char* szTemplate);
|
||||
~ConfigTemplate();
|
||||
Script* GetScript() { return m_pScript; }
|
||||
const char* GetTemplate() { return m_szTemplate; }
|
||||
};
|
||||
|
||||
typedef std::vector<ConfigTemplate*> ConfigTemplatesBase;
|
||||
// Options
|
||||
const char* GetConfigFilename() { return m_configFilename; }
|
||||
bool GetConfigErrors() { return m_configErrors; }
|
||||
const char* GetAppDir() { return m_appDir; }
|
||||
const char* GetDestDir() { return m_destDir; }
|
||||
const char* GetInterDir() { return m_interDir; }
|
||||
const char* GetTempDir() { return m_tempDir; }
|
||||
const char* GetQueueDir() { return m_queueDir; }
|
||||
const char* GetNzbDir() { return m_nzbDir; }
|
||||
const char* GetWebDir() { return m_webDir; }
|
||||
const char* GetConfigTemplate() { return m_configTemplate; }
|
||||
const char* GetScriptDir() { return m_scriptDir; }
|
||||
const char* GetRequiredDir() { return m_requiredDir; }
|
||||
bool GetNzbLog() const { return m_nzbLog; }
|
||||
EMessageTarget GetInfoTarget() const { return m_infoTarget; }
|
||||
EMessageTarget GetWarningTarget() const { return m_warningTarget; }
|
||||
EMessageTarget GetErrorTarget() const { return m_errorTarget; }
|
||||
EMessageTarget GetDebugTarget() const { return m_debugTarget; }
|
||||
EMessageTarget GetDetailTarget() const { return m_detailTarget; }
|
||||
int GetArticleTimeout() { return m_articleTimeout; }
|
||||
int GetUrlTimeout() { return m_urlTimeout; }
|
||||
int GetRemoteTimeout() { return m_remoteTimeout; }
|
||||
bool GetRawArticle() { return m_rawArticle; };
|
||||
bool GetSkipWrite() { return m_skipWrite; };
|
||||
bool GetAppendCategoryDir() { return m_appendCategoryDir; }
|
||||
bool GetContinuePartial() { return m_continuePartial; }
|
||||
int GetArticleRetries() { return m_articleRetries; }
|
||||
int GetArticleInterval() { return m_articleInterval; }
|
||||
int GetUrlRetries() { return m_urlRetries; }
|
||||
int GetUrlInterval() { return m_urlInterval; }
|
||||
bool GetFlushQueue() { return m_flushQueue; }
|
||||
bool GetDupeCheck() { return m_dupeCheck; }
|
||||
const char* GetControlIp() { return m_controlIp; }
|
||||
const char* GetControlUsername() { return m_controlUsername; }
|
||||
const char* GetControlPassword() { return m_controlPassword; }
|
||||
const char* GetRestrictedUsername() { return m_restrictedUsername; }
|
||||
const char* GetRestrictedPassword() { return m_restrictedPassword; }
|
||||
const char* GetAddUsername() { return m_addUsername; }
|
||||
const char* GetAddPassword() { return m_addPassword; }
|
||||
int GetControlPort() { return m_controlPort; }
|
||||
bool GetFormAuth() { return m_formAuth; }
|
||||
bool GetSecureControl() { return m_secureControl; }
|
||||
int GetSecurePort() { return m_securePort; }
|
||||
const char* GetSecureCert() { return m_secureCert; }
|
||||
const char* GetSecureKey() { return m_secureKey; }
|
||||
const char* GetCertStore() { return m_certStore; }
|
||||
bool GetCertCheck() { return m_certCheck; }
|
||||
const char* GetAuthorizedIp() { return m_authorizedIp; }
|
||||
const char* GetLockFile() { return m_lockFile; }
|
||||
const char* GetDaemonUsername() { return m_daemonUsername; }
|
||||
EOutputMode GetOutputMode() { return m_outputMode; }
|
||||
int GetUrlConnections() { return m_urlConnections; }
|
||||
int GetLogBuffer() { return m_logBuffer; }
|
||||
EWriteLog GetWriteLog() { return m_writeLog; }
|
||||
const char* GetLogFile() { return m_logFile; }
|
||||
int GetRotateLog() { return m_rotateLog; }
|
||||
EParCheck GetParCheck() { return m_parCheck; }
|
||||
bool GetParRepair() { return m_parRepair; }
|
||||
EParScan GetParScan() { return m_parScan; }
|
||||
bool GetParQuick() { return m_parQuick; }
|
||||
EPostStrategy GetPostStrategy() { return m_postStrategy; }
|
||||
bool GetParRename() { return m_parRename; }
|
||||
int GetParBuffer() { return m_parBuffer; }
|
||||
int GetParThreads() { return m_parThreads; }
|
||||
bool GetRarRename() { return m_rarRename; }
|
||||
EHealthCheck GetHealthCheck() { return m_healthCheck; }
|
||||
const char* GetScriptOrder() { return m_scriptOrder; }
|
||||
const char* GetExtensions() { return m_extensions; }
|
||||
int GetUMask() { return m_umask; }
|
||||
int GetUpdateInterval() {return m_updateInterval; }
|
||||
bool GetCursesNzbName() { return m_cursesNzbName; }
|
||||
bool GetCursesTime() { return m_cursesTime; }
|
||||
bool GetCursesGroup() { return m_cursesGroup; }
|
||||
bool GetCrcCheck() { return m_crcCheck; }
|
||||
bool GetDirectWrite() { return m_directWrite; }
|
||||
int GetWriteBuffer() { return m_writeBuffer; }
|
||||
int GetNzbDirInterval() { return m_nzbDirInterval; }
|
||||
int GetNzbDirFileAge() { return m_nzbDirFileAge; }
|
||||
int GetDiskSpace() { return m_diskSpace; }
|
||||
bool GetTls() { return m_tls; }
|
||||
bool GetCrashTrace() { return m_crashTrace; }
|
||||
bool GetCrashDump() { return m_crashDump; }
|
||||
bool GetParPauseQueue() { return m_parPauseQueue; }
|
||||
bool GetScriptPauseQueue() { return m_scriptPauseQueue; }
|
||||
bool GetNzbCleanupDisk() { return m_nzbCleanupDisk; }
|
||||
int GetParTimeLimit() { return m_parTimeLimit; }
|
||||
int GetKeepHistory() { return m_keepHistory; }
|
||||
bool GetUnpack() { return m_unpack; }
|
||||
bool GetDirectUnpack() { return m_directUnpack; }
|
||||
bool GetUnpackCleanupDisk() { return m_unpackCleanupDisk; }
|
||||
const char* GetUnrarCmd() { return m_unrarCmd; }
|
||||
const char* GetSevenZipCmd() { return m_sevenZipCmd; }
|
||||
const char* GetUnpackPassFile() { return m_unpackPassFile; }
|
||||
bool GetUnpackPauseQueue() { return m_unpackPauseQueue; }
|
||||
const char* GetExtCleanupDisk() { return m_extCleanupDisk; }
|
||||
const char* GetParIgnoreExt() { return m_parIgnoreExt; }
|
||||
const char* GetUnpackIgnoreExt() { return m_unpackIgnoreExt; }
|
||||
int GetFeedHistory() { return m_feedHistory; }
|
||||
bool GetUrlForce() { return m_urlForce; }
|
||||
int GetTimeCorrection() { return m_timeCorrection; }
|
||||
int GetPropagationDelay() { return m_propagationDelay; }
|
||||
int GetArticleCache() { return m_articleCache; }
|
||||
int GetEventInterval() { return m_eventInterval; }
|
||||
const char* GetShellOverride() { return m_shellOverride; }
|
||||
int GetMonthlyQuota() { return m_monthlyQuota; }
|
||||
int GetQuotaStartDay() { return m_quotaStartDay; }
|
||||
int GetDailyQuota() { return m_dailyQuota; }
|
||||
bool GetDirectRename() { return m_directRename; }
|
||||
bool GetReorderFiles() { return m_reorderFiles; }
|
||||
EFileNaming GetFileNaming() { return m_fileNaming; }
|
||||
int GetDownloadRate() const { return m_downloadRate; }
|
||||
|
||||
class ConfigTemplates: public ConfigTemplatesBase
|
||||
{
|
||||
public:
|
||||
~ConfigTemplates();
|
||||
};
|
||||
Categories* GetCategories() { return &m_categories; }
|
||||
Category* FindCategory(const char* name, bool searchAliases) { return m_categories.FindCategory(name, searchAliases); }
|
||||
|
||||
// Current state
|
||||
void SetServerMode(bool serverMode) { m_serverMode = serverMode; }
|
||||
bool GetServerMode() { return m_serverMode; }
|
||||
void SetDaemonMode(bool daemonMode) { m_daemonMode = daemonMode; }
|
||||
bool GetDaemonMode() { return m_daemonMode; }
|
||||
void SetRemoteClientMode(bool remoteClientMode) { m_remoteClientMode = remoteClientMode; }
|
||||
bool GetRemoteClientMode() { return m_remoteClientMode; }
|
||||
|
||||
private:
|
||||
OptEntries m_OptEntries;
|
||||
bool m_bConfigInitialized;
|
||||
Mutex m_mutexOptEntries;
|
||||
Categories m_Categories;
|
||||
Scripts m_Scripts;
|
||||
ConfigTemplates m_ConfigTemplates;
|
||||
OptEntries m_optEntries;
|
||||
Mutex m_optEntriesMutex;
|
||||
Categories m_categories;
|
||||
bool m_noDiskAccess = false;
|
||||
bool m_noConfig = false;
|
||||
bool m_fatalError = false;
|
||||
Extender* m_extender;
|
||||
|
||||
// Options
|
||||
bool m_bConfigErrors;
|
||||
int m_iConfigLine;
|
||||
char* m_szConfigFilename;
|
||||
char* m_szDestDir;
|
||||
char* m_szInterDir;
|
||||
char* m_szTempDir;
|
||||
char* m_szQueueDir;
|
||||
char* m_szNzbDir;
|
||||
char* m_szWebDir;
|
||||
char* m_szConfigTemplate;
|
||||
char* m_szScriptDir;
|
||||
EMessageTarget m_eInfoTarget;
|
||||
EMessageTarget m_eWarningTarget;
|
||||
EMessageTarget m_eErrorTarget;
|
||||
EMessageTarget m_eDebugTarget;
|
||||
EMessageTarget m_eDetailTarget;
|
||||
bool m_bDecode;
|
||||
bool m_bCreateBrokenLog;
|
||||
int m_iArticleTimeout;
|
||||
int m_iUrlTimeout;
|
||||
int m_iTerminateTimeout;
|
||||
bool m_bAppendCategoryDir;
|
||||
bool m_bContinuePartial;
|
||||
int m_iRetries;
|
||||
int m_iRetryInterval;
|
||||
bool m_bSaveQueue;
|
||||
bool m_bDupeCheck;
|
||||
char* m_szControlIP;
|
||||
char* m_szControlUsername;
|
||||
char* m_szControlPassword;
|
||||
int m_iControlPort;
|
||||
bool m_bSecureControl;
|
||||
int m_iSecurePort;
|
||||
char* m_szSecureCert;
|
||||
char* m_szSecureKey;
|
||||
char* m_szAuthorizedIP;
|
||||
char* m_szLockFile;
|
||||
char* m_szDaemonUsername;
|
||||
EOutputMode m_eOutputMode;
|
||||
bool m_bReloadQueue;
|
||||
int m_iUrlConnections;
|
||||
int m_iLogBufferSize;
|
||||
EWriteLog m_eWriteLog;
|
||||
int m_iRotateLog;
|
||||
char* m_szLogFile;
|
||||
EParCheck m_eParCheck;
|
||||
bool m_bParRepair;
|
||||
EParScan m_eParScan;
|
||||
bool m_bParQuick;
|
||||
bool m_bParRename;
|
||||
int m_iParBuffer;
|
||||
int m_iParThreads;
|
||||
EHealthCheck m_eHealthCheck;
|
||||
char* m_szPostScript;
|
||||
char* m_szScriptOrder;
|
||||
char* m_szScanScript;
|
||||
char* m_szQueueScript;
|
||||
bool m_bNoConfig;
|
||||
int m_iUMask;
|
||||
int m_iUpdateInterval;
|
||||
bool m_bCursesNZBName;
|
||||
bool m_bCursesTime;
|
||||
bool m_bCursesGroup;
|
||||
bool m_bCrcCheck;
|
||||
bool m_bDirectWrite;
|
||||
int m_iWriteBuffer;
|
||||
int m_iNzbDirInterval;
|
||||
int m_iNzbDirFileAge;
|
||||
bool m_bParCleanupQueue;
|
||||
int m_iDiskSpace;
|
||||
bool m_bTLS;
|
||||
bool m_bDumpCore;
|
||||
bool m_bParPauseQueue;
|
||||
bool m_bScriptPauseQueue;
|
||||
bool m_bNzbCleanupDisk;
|
||||
bool m_bDeleteCleanupDisk;
|
||||
int m_iParTimeLimit;
|
||||
int m_iKeepHistory;
|
||||
bool m_bAccurateRate;
|
||||
bool m_bUnpack;
|
||||
bool m_bUnpackCleanupDisk;
|
||||
char* m_szUnrarCmd;
|
||||
char* m_szSevenZipCmd;
|
||||
bool m_bUnpackPauseQueue;
|
||||
char* m_szExtCleanupDisk;
|
||||
char* m_szParIgnoreExt;
|
||||
int m_iFeedHistory;
|
||||
bool m_bUrlForce;
|
||||
int m_iTimeCorrection;
|
||||
int m_iPropagationDelay;
|
||||
int m_iArticleCache;
|
||||
int m_iEventInterval;
|
||||
bool m_configErrors = false;
|
||||
int m_configLine = 0;
|
||||
CString m_appDir;
|
||||
CString m_configFilename;
|
||||
CString m_destDir;
|
||||
CString m_interDir;
|
||||
CString m_tempDir;
|
||||
CString m_queueDir;
|
||||
CString m_nzbDir;
|
||||
CString m_webDir;
|
||||
CString m_configTemplate;
|
||||
CString m_scriptDir;
|
||||
CString m_requiredDir;
|
||||
EMessageTarget m_infoTarget = mtScreen;
|
||||
EMessageTarget m_warningTarget = mtScreen;
|
||||
EMessageTarget m_errorTarget = mtScreen;
|
||||
EMessageTarget m_debugTarget = mtNone;
|
||||
EMessageTarget m_detailTarget = mtScreen;
|
||||
bool m_skipWrite = false;
|
||||
bool m_rawArticle = false;
|
||||
bool m_nzbLog = false;
|
||||
int m_articleTimeout = 0;
|
||||
int m_urlTimeout = 0;
|
||||
int m_remoteTimeout = 0;
|
||||
bool m_appendCategoryDir = false;
|
||||
bool m_continuePartial = false;
|
||||
int m_articleRetries = 0;
|
||||
int m_articleInterval = 0;
|
||||
int m_urlRetries = 0;
|
||||
int m_urlInterval = 0;
|
||||
bool m_flushQueue = false;
|
||||
bool m_dupeCheck = false;
|
||||
CString m_controlIp;
|
||||
CString m_controlUsername;
|
||||
CString m_controlPassword;
|
||||
CString m_restrictedUsername;
|
||||
CString m_restrictedPassword;
|
||||
CString m_addUsername;
|
||||
CString m_addPassword;
|
||||
bool m_formAuth = false;
|
||||
int m_controlPort = 0;
|
||||
bool m_secureControl = false;
|
||||
int m_securePort = 0;
|
||||
CString m_secureCert;
|
||||
CString m_secureKey;
|
||||
CString m_certStore;
|
||||
bool m_certCheck = false;
|
||||
CString m_authorizedIp;
|
||||
CString m_lockFile;
|
||||
CString m_daemonUsername;
|
||||
EOutputMode m_outputMode = omLoggable;
|
||||
int m_urlConnections = 0;
|
||||
int m_logBuffer = 0;
|
||||
EWriteLog m_writeLog = wlAppend;
|
||||
int m_rotateLog = 0;
|
||||
CString m_logFile;
|
||||
EParCheck m_parCheck = pcManual;
|
||||
bool m_parRepair = false;
|
||||
EParScan m_parScan = psLimited;
|
||||
bool m_parQuick = true;
|
||||
EPostStrategy m_postStrategy = ppSequential;
|
||||
bool m_parRename = false;
|
||||
int m_parBuffer = 0;
|
||||
int m_parThreads = 0;
|
||||
bool m_rarRename = false;
|
||||
bool m_directRename = false;
|
||||
EHealthCheck m_healthCheck = hcNone;
|
||||
CString m_extensions;
|
||||
CString m_scriptOrder;
|
||||
int m_umask = 0;
|
||||
int m_updateInterval = 0;
|
||||
bool m_cursesNzbName = false;
|
||||
bool m_cursesTime = false;
|
||||
bool m_cursesGroup = false;
|
||||
bool m_crcCheck = false;
|
||||
bool m_directWrite = false;
|
||||
int m_writeBuffer = 0;
|
||||
int m_nzbDirInterval = 0;
|
||||
int m_nzbDirFileAge = 0;
|
||||
int m_diskSpace = 0;
|
||||
bool m_tls = false;
|
||||
bool m_crashTrace = false;
|
||||
bool m_crashDump = false;
|
||||
bool m_parPauseQueue = false;
|
||||
bool m_scriptPauseQueue = false;
|
||||
bool m_nzbCleanupDisk = false;
|
||||
int m_parTimeLimit = 0;
|
||||
int m_keepHistory = 0;
|
||||
bool m_unpack = false;
|
||||
bool m_directUnpack = false;
|
||||
bool m_unpackCleanupDisk = false;
|
||||
CString m_unrarCmd;
|
||||
CString m_sevenZipCmd;
|
||||
CString m_unpackPassFile;
|
||||
bool m_unpackPauseQueue;
|
||||
CString m_extCleanupDisk;
|
||||
CString m_parIgnoreExt;
|
||||
CString m_unpackIgnoreExt;
|
||||
int m_feedHistory = 0;
|
||||
bool m_urlForce = false;
|
||||
int m_timeCorrection = 0;
|
||||
int m_propagationDelay = 0;
|
||||
int m_articleCache = 0;
|
||||
int m_eventInterval = 0;
|
||||
CString m_shellOverride;
|
||||
int m_monthlyQuota = 0;
|
||||
int m_quotaStartDay = 0;
|
||||
int m_dailyQuota = 0;
|
||||
bool m_reorderFiles = false;
|
||||
EFileNaming m_fileNaming = nfArticle;
|
||||
int m_downloadRate = 0;
|
||||
|
||||
// Parsed command-line parameters
|
||||
bool m_bServerMode;
|
||||
bool m_bDaemonMode;
|
||||
bool m_bRemoteClientMode;
|
||||
int m_iEditQueueAction;
|
||||
int m_iEditQueueOffset;
|
||||
int* m_pEditQueueIDList;
|
||||
int m_iEditQueueIDCount;
|
||||
NameList m_EditQueueNameList;
|
||||
EMatchMode m_EMatchMode;
|
||||
char* m_szEditQueueText;
|
||||
char* m_szArgFilename;
|
||||
char* m_szAddCategory;
|
||||
int m_iAddPriority;
|
||||
bool m_bAddPaused;
|
||||
char* m_szAddNZBFilename;
|
||||
char* m_szLastArg;
|
||||
bool m_bPrintOptions;
|
||||
bool m_bAddTop;
|
||||
int m_iSetRate;
|
||||
int m_iLogLines;
|
||||
int m_iWriteLogKind;
|
||||
bool m_bTestBacktrace;
|
||||
// Application mode
|
||||
bool m_serverMode = false;
|
||||
bool m_daemonMode = false;
|
||||
bool m_remoteClientMode = false;
|
||||
|
||||
// Current state
|
||||
bool m_bPauseDownload;
|
||||
bool m_bPausePostProcess;
|
||||
bool m_bPauseScan;
|
||||
bool m_bTempPauseDownload;
|
||||
int m_iDownloadRate;
|
||||
EClientOperation m_eClientOperation;
|
||||
time_t m_tResumeTime;
|
||||
int m_iLocalTimeOffset;
|
||||
|
||||
void InitDefault();
|
||||
void InitOptFile();
|
||||
void InitCommandLine(int argc, char* argv[]);
|
||||
void InitOptions();
|
||||
void InitFileArg(int argc, char* argv[]);
|
||||
void InitServers();
|
||||
void InitCategories();
|
||||
void InitScheduler();
|
||||
void InitFeeds();
|
||||
void InitScripts();
|
||||
void InitConfigTemplates();
|
||||
void CheckOptions();
|
||||
void PrintUsage(char* com);
|
||||
void Dump();
|
||||
int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]);
|
||||
int ParseIntValue(const char* OptName, int iBase);
|
||||
float ParseFloatValue(const char* OptName);
|
||||
OptEntry* FindOption(const char* optname);
|
||||
const char* GetOption(const char* optname);
|
||||
void SetOption(const char* optname, const char* value);
|
||||
bool SetOptionString(const char* option);
|
||||
bool SplitOptionString(const char* option, char** pOptName, char** pOptValue);
|
||||
bool ValidateOptionName(const char* optname, const char* optvalue);
|
||||
void LoadConfigFile();
|
||||
void CheckDir(char** dir, const char* szOptionName, const char* szParentDir,
|
||||
bool bAllowEmpty, bool bCreate);
|
||||
void ParseFileIDList(int argc, char* argv[], int optind);
|
||||
void ParseFileNameList(int argc, char* argv[], int optind);
|
||||
bool ParseTime(const char* szTime, int* pHours, int* pMinutes);
|
||||
bool ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits);
|
||||
void ConfigError(const char* msg, ...);
|
||||
void ConfigWarn(const char* msg, ...);
|
||||
void LocateOptionSrcPos(const char *szOptionName);
|
||||
void ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen);
|
||||
static bool CompareScripts(Script* pScript1, Script* pScript2);
|
||||
void LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir);
|
||||
void BuildScriptDisplayNames(Scripts* pScripts);
|
||||
void LoadScripts(Scripts* pScripts);
|
||||
|
||||
public:
|
||||
Options(int argc, char* argv[]);
|
||||
~Options();
|
||||
|
||||
bool LoadConfig(OptEntries* pOptEntries);
|
||||
bool SaveConfig(OptEntries* pOptEntries);
|
||||
bool LoadConfigTemplates(ConfigTemplates* pConfigTemplates);
|
||||
Scripts* GetScripts() { return &m_Scripts; }
|
||||
ConfigTemplates* GetConfigTemplates() { return &m_ConfigTemplates; }
|
||||
|
||||
// Options
|
||||
OptEntries* LockOptEntries();
|
||||
void UnlockOptEntries();
|
||||
const char* GetConfigFilename() { return m_szConfigFilename; }
|
||||
const char* GetDestDir() { return m_szDestDir; }
|
||||
const char* GetInterDir() { return m_szInterDir; }
|
||||
const char* GetTempDir() { return m_szTempDir; }
|
||||
const char* GetQueueDir() { return m_szQueueDir; }
|
||||
const char* GetNzbDir() { return m_szNzbDir; }
|
||||
const char* GetWebDir() { return m_szWebDir; }
|
||||
const char* GetConfigTemplate() { return m_szConfigTemplate; }
|
||||
const char* GetScriptDir() { return m_szScriptDir; }
|
||||
bool GetCreateBrokenLog() const { return m_bCreateBrokenLog; }
|
||||
EMessageTarget GetInfoTarget() const { return m_eInfoTarget; }
|
||||
EMessageTarget GetWarningTarget() const { return m_eWarningTarget; }
|
||||
EMessageTarget GetErrorTarget() const { return m_eErrorTarget; }
|
||||
EMessageTarget GetDebugTarget() const { return m_eDebugTarget; }
|
||||
EMessageTarget GetDetailTarget() const { return m_eDetailTarget; }
|
||||
int GetArticleTimeout() { return m_iArticleTimeout; }
|
||||
int GetUrlTimeout() { return m_iUrlTimeout; }
|
||||
int GetTerminateTimeout() { return m_iTerminateTimeout; }
|
||||
bool GetDecode() { return m_bDecode; };
|
||||
bool GetAppendCategoryDir() { return m_bAppendCategoryDir; }
|
||||
bool GetContinuePartial() { return m_bContinuePartial; }
|
||||
int GetRetries() { return m_iRetries; }
|
||||
int GetRetryInterval() { return m_iRetryInterval; }
|
||||
bool GetSaveQueue() { return m_bSaveQueue; }
|
||||
bool GetDupeCheck() { return m_bDupeCheck; }
|
||||
const char* GetControlIP();
|
||||
const char* GetControlUsername() { return m_szControlUsername; }
|
||||
const char* GetControlPassword() { return m_szControlPassword; }
|
||||
int GetControlPort() { return m_iControlPort; }
|
||||
bool GetSecureControl() { return m_bSecureControl; }
|
||||
int GetSecurePort() { return m_iSecurePort; }
|
||||
const char* GetSecureCert() { return m_szSecureCert; }
|
||||
const char* GetSecureKey() { return m_szSecureKey; }
|
||||
const char* GetAuthorizedIP() { return m_szAuthorizedIP; }
|
||||
const char* GetLockFile() { return m_szLockFile; }
|
||||
const char* GetDaemonUsername() { return m_szDaemonUsername; }
|
||||
EOutputMode GetOutputMode() { return m_eOutputMode; }
|
||||
bool GetReloadQueue() { return m_bReloadQueue; }
|
||||
int GetUrlConnections() { return m_iUrlConnections; }
|
||||
int GetLogBufferSize() { return m_iLogBufferSize; }
|
||||
EWriteLog GetWriteLog() { return m_eWriteLog; }
|
||||
const char* GetLogFile() { return m_szLogFile; }
|
||||
int GetRotateLog() { return m_iRotateLog; }
|
||||
EParCheck GetParCheck() { return m_eParCheck; }
|
||||
bool GetParRepair() { return m_bParRepair; }
|
||||
EParScan GetParScan() { return m_eParScan; }
|
||||
bool GetParQuick() { return m_bParQuick; }
|
||||
bool GetParRename() { return m_bParRename; }
|
||||
int GetParBuffer() { return m_iParBuffer; }
|
||||
int GetParThreads() { return m_iParThreads; }
|
||||
EHealthCheck GetHealthCheck() { return m_eHealthCheck; }
|
||||
const char* GetScriptOrder() { return m_szScriptOrder; }
|
||||
const char* GetPostScript() { return m_szPostScript; }
|
||||
const char* GetScanScript() { return m_szScanScript; }
|
||||
const char* GetQueueScript() { return m_szQueueScript; }
|
||||
int GetUMask() { return m_iUMask; }
|
||||
int GetUpdateInterval() {return m_iUpdateInterval; }
|
||||
bool GetCursesNZBName() { return m_bCursesNZBName; }
|
||||
bool GetCursesTime() { return m_bCursesTime; }
|
||||
bool GetCursesGroup() { return m_bCursesGroup; }
|
||||
bool GetCrcCheck() { return m_bCrcCheck; }
|
||||
bool GetDirectWrite() { return m_bDirectWrite; }
|
||||
int GetWriteBuffer() { return m_iWriteBuffer; }
|
||||
int GetNzbDirInterval() { return m_iNzbDirInterval; }
|
||||
int GetNzbDirFileAge() { return m_iNzbDirFileAge; }
|
||||
bool GetParCleanupQueue() { return m_bParCleanupQueue; }
|
||||
int GetDiskSpace() { return m_iDiskSpace; }
|
||||
bool GetTLS() { return m_bTLS; }
|
||||
bool GetDumpCore() { return m_bDumpCore; }
|
||||
bool GetParPauseQueue() { return m_bParPauseQueue; }
|
||||
bool GetScriptPauseQueue() { return m_bScriptPauseQueue; }
|
||||
bool GetNzbCleanupDisk() { return m_bNzbCleanupDisk; }
|
||||
bool GetDeleteCleanupDisk() { return m_bDeleteCleanupDisk; }
|
||||
int GetParTimeLimit() { return m_iParTimeLimit; }
|
||||
int GetKeepHistory() { return m_iKeepHistory; }
|
||||
bool GetAccurateRate() { return m_bAccurateRate; }
|
||||
bool GetUnpack() { return m_bUnpack; }
|
||||
bool GetUnpackCleanupDisk() { return m_bUnpackCleanupDisk; }
|
||||
const char* GetUnrarCmd() { return m_szUnrarCmd; }
|
||||
const char* GetSevenZipCmd() { return m_szSevenZipCmd; }
|
||||
bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; }
|
||||
const char* GetExtCleanupDisk() { return m_szExtCleanupDisk; }
|
||||
const char* GetParIgnoreExt() { return m_szParIgnoreExt; }
|
||||
int GetFeedHistory() { return m_iFeedHistory; }
|
||||
bool GetUrlForce() { return m_bUrlForce; }
|
||||
int GetTimeCorrection() { return m_iTimeCorrection; }
|
||||
int GetPropagationDelay() { return m_iPropagationDelay; }
|
||||
int GetArticleCache() { return m_iArticleCache; }
|
||||
int GetEventInterval() { return m_iEventInterval; }
|
||||
|
||||
Category* FindCategory(const char* szName, bool bSearchAliases) { return m_Categories.FindCategory(szName, bSearchAliases); }
|
||||
|
||||
// Parsed command-line parameters
|
||||
bool GetServerMode() { return m_bServerMode; }
|
||||
bool GetDaemonMode() { return m_bDaemonMode; }
|
||||
bool GetRemoteClientMode() { return m_bRemoteClientMode; }
|
||||
EClientOperation GetClientOperation() { return m_eClientOperation; }
|
||||
int GetEditQueueAction() { return m_iEditQueueAction; }
|
||||
int GetEditQueueOffset() { return m_iEditQueueOffset; }
|
||||
int* GetEditQueueIDList() { return m_pEditQueueIDList; }
|
||||
int GetEditQueueIDCount() { return m_iEditQueueIDCount; }
|
||||
NameList* GetEditQueueNameList() { return &m_EditQueueNameList; }
|
||||
EMatchMode GetMatchMode() { return m_EMatchMode; }
|
||||
const char* GetEditQueueText() { return m_szEditQueueText; }
|
||||
const char* GetArgFilename() { return m_szArgFilename; }
|
||||
const char* GetAddCategory() { return m_szAddCategory; }
|
||||
bool GetAddPaused() { return m_bAddPaused; }
|
||||
const char* GetLastArg() { return m_szLastArg; }
|
||||
int GetAddPriority() { return m_iAddPriority; }
|
||||
char* GetAddNZBFilename() { return m_szAddNZBFilename; }
|
||||
bool GetAddTop() { return m_bAddTop; }
|
||||
int GetSetRate() { return m_iSetRate; }
|
||||
int GetLogLines() { return m_iLogLines; }
|
||||
int GetWriteLogKind() { return m_iWriteLogKind; }
|
||||
bool GetTestBacktrace() { return m_bTestBacktrace; }
|
||||
|
||||
// Current state
|
||||
void SetPauseDownload(bool bPauseDownload) { m_bPauseDownload = bPauseDownload; }
|
||||
bool GetPauseDownload() const { return m_bPauseDownload; }
|
||||
void SetPausePostProcess(bool bPausePostProcess) { m_bPausePostProcess = bPausePostProcess; }
|
||||
bool GetPausePostProcess() const { return m_bPausePostProcess; }
|
||||
void SetPauseScan(bool bPauseScan) { m_bPauseScan = bPauseScan; }
|
||||
bool GetPauseScan() const { return m_bPauseScan; }
|
||||
void SetTempPauseDownload(bool bTempPauseDownload) { m_bTempPauseDownload = bTempPauseDownload; }
|
||||
bool GetTempPauseDownload() const { return m_bTempPauseDownload; }
|
||||
void SetDownloadRate(int iRate) { m_iDownloadRate = iRate; }
|
||||
int GetDownloadRate() const { return m_iDownloadRate; }
|
||||
void SetResumeTime(time_t tResumeTime) { m_tResumeTime = tResumeTime; }
|
||||
time_t GetResumeTime() const { return m_tResumeTime; }
|
||||
void SetLocalTimeOffset(int iLocalTimeOffset) { m_iLocalTimeOffset = iLocalTimeOffset; }
|
||||
int GetLocalTimeOffset() { return m_iLocalTimeOffset; }
|
||||
void Init(const char* exeName, const char* configFilename, bool noConfig,
|
||||
CmdOptList* commandLineOptions, bool noDiskAccess, Extender* extender);
|
||||
void InitDefaults();
|
||||
void InitOptions();
|
||||
void InitOptFile();
|
||||
void InitServers();
|
||||
void InitCategories();
|
||||
void InitScheduler();
|
||||
void InitFeeds();
|
||||
void InitCommandLineOptions(CmdOptList* commandLineOptions);
|
||||
void CheckOptions();
|
||||
int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]);
|
||||
int ParseIntValue(const char* OptName, int base);
|
||||
OptEntry* FindOption(const char* optname);
|
||||
const char* GetOption(const char* optname);
|
||||
void SetOption(const char* optname, const char* value);
|
||||
bool SetOptionString(const char* option);
|
||||
bool ValidateOptionName(const char* optname, const char* optvalue);
|
||||
void LoadConfigFile();
|
||||
void CheckDir(CString& dir, const char* optionName, const char* parentDir,
|
||||
bool allowEmpty, bool create);
|
||||
bool ParseTime(const char* time, int* hours, int* minutes);
|
||||
bool ParseWeekDays(const char* weekDays, int* weekDaysBits);
|
||||
void ConfigError(const char* msg, ...);
|
||||
void ConfigWarn(const char* msg, ...);
|
||||
void LocateOptionSrcPos(const char *optionName);
|
||||
static void ConvertOldOption(CString& option, CString& value);
|
||||
static void MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories);
|
||||
static bool HasScript(const char* scriptList, const char* scriptName);
|
||||
};
|
||||
|
||||
extern Options* g_Options;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2008-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,266 +14,227 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Scheduler.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "Log.h"
|
||||
#include "NewsServer.h"
|
||||
#include "ServerPool.h"
|
||||
#include "FeedInfo.h"
|
||||
#include "FeedCoordinator.h"
|
||||
#include "QueueScript.h"
|
||||
#include "SchedulerScript.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
extern ServerPool* g_pServerPool;
|
||||
extern FeedCoordinator* g_pFeedCoordinator;
|
||||
|
||||
class SchedulerScriptController : public Thread, public NZBScriptController
|
||||
void Scheduler::AddTask(std::unique_ptr<Task> task)
|
||||
{
|
||||
private:
|
||||
char* m_szScript;
|
||||
bool m_bExternalProcess;
|
||||
int m_iTaskID;
|
||||
|
||||
void PrepareParams(const char* szScriptName);
|
||||
void ExecuteExternalProcess();
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(Options::Script* pScript);
|
||||
|
||||
public:
|
||||
virtual ~SchedulerScriptController();
|
||||
virtual void Run();
|
||||
static void StartScript(const char* szParam, bool bExternalProcess, int iTaskID);
|
||||
};
|
||||
|
||||
Scheduler::Task::Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam)
|
||||
{
|
||||
m_iID = iID;
|
||||
m_iHours = iHours;
|
||||
m_iMinutes = iMinutes;
|
||||
m_iWeekDaysBits = iWeekDaysBits;
|
||||
m_eCommand = eCommand;
|
||||
m_szParam = szParam ? strdup(szParam) : NULL;
|
||||
m_tLastExecuted = 0;
|
||||
}
|
||||
|
||||
Scheduler::Task::~Task()
|
||||
{
|
||||
free(m_szParam);
|
||||
}
|
||||
|
||||
|
||||
Scheduler::Scheduler()
|
||||
{
|
||||
debug("Creating Scheduler");
|
||||
|
||||
m_tLastCheck = 0;
|
||||
m_TaskList.clear();
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler()
|
||||
{
|
||||
debug("Destroying Scheduler");
|
||||
|
||||
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::AddTask(Task* pTask)
|
||||
{
|
||||
m_mutexTaskList.Lock();
|
||||
m_TaskList.push_back(pTask);
|
||||
m_mutexTaskList.Unlock();
|
||||
}
|
||||
|
||||
bool Scheduler::CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2)
|
||||
{
|
||||
return (pTask1->m_iHours < pTask2->m_iHours) ||
|
||||
((pTask1->m_iHours == pTask2->m_iHours) && (pTask1->m_iMinutes < pTask2->m_iMinutes));
|
||||
Guard guard(m_taskListMutex);
|
||||
m_taskList.push_back(std::move(task));
|
||||
}
|
||||
|
||||
void Scheduler::FirstCheck()
|
||||
{
|
||||
m_mutexTaskList.Lock();
|
||||
m_TaskList.sort(CompareTasks);
|
||||
m_mutexTaskList.Unlock();
|
||||
{
|
||||
Guard guard(m_taskListMutex);
|
||||
|
||||
std::sort(m_taskList.begin(), m_taskList.end(),
|
||||
[](const std::unique_ptr<Task>& task1, const std::unique_ptr<Task>& task2)
|
||||
{
|
||||
return (task1->m_hours < task2->m_hours) ||
|
||||
((task1->m_hours == task2->m_hours) && (task1->m_minutes < task2->m_minutes));
|
||||
});
|
||||
}
|
||||
|
||||
// check all tasks for the last week
|
||||
CheckTasks();
|
||||
}
|
||||
|
||||
void Scheduler::IntervalCheck()
|
||||
void Scheduler::ScheduleNextWork()
|
||||
{
|
||||
m_bExecuteProcess = true;
|
||||
// Ideally we should calculate wait time until next scheduler task or until resume time.
|
||||
// The first isn't trivial and the second requires watching/reaction on changed scheduled resume time.
|
||||
// We do it simpler instead: check once per minute, when seconds are changing from 59 to 00.
|
||||
|
||||
time_t curTime = Util::CurrentTime();
|
||||
tm sched;
|
||||
gmtime_r(&curTime, &sched);
|
||||
sched.tm_min++;
|
||||
sched.tm_sec = 0;
|
||||
time_t nextMinute = Util::Timegm(&sched);
|
||||
|
||||
m_serviceInterval = nextMinute - curTime;
|
||||
}
|
||||
|
||||
void Scheduler::ServiceWork()
|
||||
{
|
||||
debug("Scheduler service work");
|
||||
|
||||
if (!DownloadQueue::IsLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Scheduler service work: doing work");
|
||||
|
||||
if (!m_firstChecked)
|
||||
{
|
||||
FirstCheck();
|
||||
m_firstChecked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_executeProcess = true;
|
||||
CheckTasks();
|
||||
CheckScheduledResume();
|
||||
ScheduleNextWork();
|
||||
}
|
||||
|
||||
void Scheduler::CheckTasks()
|
||||
{
|
||||
PrepareLog();
|
||||
|
||||
m_mutexTaskList.Lock();
|
||||
|
||||
time_t tCurrent = time(NULL);
|
||||
|
||||
if (!m_TaskList.empty())
|
||||
{
|
||||
// Detect large step changes of system time
|
||||
time_t tDiff = tCurrent - m_tLastCheck;
|
||||
if (tDiff > 60*90 || tDiff < 0)
|
||||
Guard guard(m_taskListMutex);
|
||||
|
||||
time_t current = Util::CurrentTime();
|
||||
|
||||
if (!m_taskList.empty())
|
||||
{
|
||||
debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)");
|
||||
|
||||
// check all tasks for the last week
|
||||
m_tLastCheck = tCurrent - 60*60*24*7;
|
||||
m_bExecuteProcess = false;
|
||||
|
||||
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
|
||||
// Detect large step changes of system time
|
||||
time_t diff = current - m_lastCheck;
|
||||
if (diff > 60 * 90 || diff < 0)
|
||||
{
|
||||
Task* pTask = *it;
|
||||
pTask->m_tLastExecuted = 0;
|
||||
}
|
||||
}
|
||||
debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)");
|
||||
|
||||
time_t tLocalCurrent = tCurrent + g_pOptions->GetLocalTimeOffset();
|
||||
time_t tLocalLastCheck = m_tLastCheck + g_pOptions->GetLocalTimeOffset();
|
||||
// check all tasks for the last week
|
||||
m_lastCheck = current - 60 * 60 * 24 * 7;
|
||||
m_executeProcess = false;
|
||||
|
||||
tm tmCurrent;
|
||||
gmtime_r(&tLocalCurrent, &tmCurrent);
|
||||
tm tmLastCheck;
|
||||
gmtime_r(&tLocalLastCheck, &tmLastCheck);
|
||||
|
||||
tm tmLoop;
|
||||
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
|
||||
tmLoop.tm_hour = tmCurrent.tm_hour;
|
||||
tmLoop.tm_min = tmCurrent.tm_min;
|
||||
tmLoop.tm_sec = tmCurrent.tm_sec;
|
||||
time_t tLoop = Util::Timegm(&tmLoop);
|
||||
|
||||
while (tLoop <= tLocalCurrent)
|
||||
{
|
||||
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
|
||||
{
|
||||
Task* pTask = *it;
|
||||
if (pTask->m_tLastExecuted != tLoop)
|
||||
for (Task* task : &m_taskList)
|
||||
{
|
||||
tm tmAppoint;
|
||||
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
|
||||
tmAppoint.tm_hour = pTask->m_iHours;
|
||||
tmAppoint.tm_min = pTask->m_iMinutes;
|
||||
tmAppoint.tm_sec = 0;
|
||||
|
||||
time_t tAppoint = Util::Timegm(&tmAppoint);
|
||||
|
||||
int iWeekDay = tmAppoint.tm_wday;
|
||||
if (iWeekDay == 0)
|
||||
if (task->m_hours != Task::STARTUP_TASK)
|
||||
{
|
||||
iWeekDay = 7;
|
||||
}
|
||||
|
||||
bool bWeekDayOK = pTask->m_iWeekDaysBits == 0 || (pTask->m_iWeekDaysBits & (1 << (iWeekDay - 1)));
|
||||
bool bDoTask = bWeekDayOK && tLocalLastCheck < tAppoint && tAppoint <= tLocalCurrent;
|
||||
|
||||
//debug("TEMP: 1) m_tLastCheck=%i, tLocalCurrent=%i, tLoop=%i, tAppoint=%i, bWeekDayOK=%i, bDoTask=%i", m_tLastCheck, tLocalCurrent, tLoop, tAppoint, (int)bWeekDayOK, (int)bDoTask);
|
||||
|
||||
if (bDoTask)
|
||||
{
|
||||
ExecuteTask(pTask);
|
||||
pTask->m_tLastExecuted = tLoop;
|
||||
task->m_lastExecuted = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
tLoop += 60*60*24; // inc day
|
||||
gmtime_r(&tLoop, &tmLoop);
|
||||
|
||||
time_t localCurrent = current + g_WorkState->GetLocalTimeOffset();
|
||||
time_t localLastCheck = m_lastCheck + g_WorkState->GetLocalTimeOffset();
|
||||
|
||||
tm tmCurrent;
|
||||
gmtime_r(&localCurrent, &tmCurrent);
|
||||
tm tmLastCheck;
|
||||
gmtime_r(&localLastCheck, &tmLastCheck);
|
||||
|
||||
tm tmLoop;
|
||||
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
|
||||
tmLoop.tm_hour = tmCurrent.tm_hour;
|
||||
tmLoop.tm_min = tmCurrent.tm_min;
|
||||
tmLoop.tm_sec = tmCurrent.tm_sec;
|
||||
time_t loop = Util::Timegm(&tmLoop);
|
||||
|
||||
while (loop <= localCurrent)
|
||||
{
|
||||
for (Task* task : &m_taskList)
|
||||
{
|
||||
if (task->m_lastExecuted != loop)
|
||||
{
|
||||
tm tmAppoint;
|
||||
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
|
||||
tmAppoint.tm_hour = task->m_hours;
|
||||
tmAppoint.tm_min = task->m_minutes;
|
||||
tmAppoint.tm_sec = 0;
|
||||
|
||||
time_t appoint = Util::Timegm(&tmAppoint);
|
||||
|
||||
int weekDay = tmAppoint.tm_wday;
|
||||
if (weekDay == 0)
|
||||
{
|
||||
weekDay = 7;
|
||||
}
|
||||
|
||||
bool weekDayOK = task->m_weekDaysBits == 0 || (task->m_weekDaysBits & (1 << (weekDay - 1)));
|
||||
bool doTask = (task->m_hours >= 0 && weekDayOK && localLastCheck < appoint && appoint <= localCurrent) ||
|
||||
(task->m_hours == Task::STARTUP_TASK && task->m_lastExecuted == 0);
|
||||
|
||||
if (doTask)
|
||||
{
|
||||
ExecuteTask(task);
|
||||
task->m_lastExecuted = loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
loop += 60 * 60 * 24; // inc day
|
||||
gmtime_r(&loop, &tmLoop);
|
||||
}
|
||||
}
|
||||
|
||||
m_lastCheck = current;
|
||||
}
|
||||
|
||||
m_tLastCheck = tCurrent;
|
||||
|
||||
m_mutexTaskList.Unlock();
|
||||
|
||||
PrintLog();
|
||||
}
|
||||
|
||||
void Scheduler::ExecuteTask(Task* pTask)
|
||||
void Scheduler::ExecuteTask(Task* task)
|
||||
{
|
||||
const char* szCommandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing",
|
||||
#ifdef DEBUG
|
||||
const char* commandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing",
|
||||
"Set download rate", "Execute process", "Execute script",
|
||||
"Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" };
|
||||
debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]);
|
||||
debug("Executing scheduled command: %s", commandName[task->m_command]);
|
||||
#endif
|
||||
|
||||
switch (pTask->m_eCommand)
|
||||
bool executeProcess = m_executeProcess || task->m_hours == Task::STARTUP_TASK;
|
||||
|
||||
switch (task->m_command)
|
||||
{
|
||||
case scDownloadRate:
|
||||
if (!Util::EmptyStr(pTask->m_szParam))
|
||||
if (!task->m_param.Empty())
|
||||
{
|
||||
g_pOptions->SetDownloadRate(atoi(pTask->m_szParam) * 1024);
|
||||
m_bDownloadRateChanged = true;
|
||||
g_WorkState->SetSpeedLimit(atoi(task->m_param) * 1024);
|
||||
m_downloadRateChanged = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case scPauseDownload:
|
||||
case scUnpauseDownload:
|
||||
g_pOptions->SetPauseDownload(pTask->m_eCommand == scPauseDownload);
|
||||
m_bPauseDownloadChanged = true;
|
||||
g_WorkState->SetPauseDownload(task->m_command == scPauseDownload);
|
||||
m_pauseDownloadChanged = true;
|
||||
break;
|
||||
|
||||
case scPausePostProcess:
|
||||
case scUnpausePostProcess:
|
||||
g_pOptions->SetPausePostProcess(pTask->m_eCommand == scPausePostProcess);
|
||||
m_bPausePostProcessChanged = true;
|
||||
g_WorkState->SetPausePostProcess(task->m_command == scPausePostProcess);
|
||||
m_pausePostProcessChanged = true;
|
||||
break;
|
||||
|
||||
case scPauseScan:
|
||||
case scUnpauseScan:
|
||||
g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan);
|
||||
m_bPauseScanChanged = true;
|
||||
g_WorkState->SetPauseScan(task->m_command == scPauseScan);
|
||||
m_pauseScanChanged = true;
|
||||
break;
|
||||
|
||||
case scScript:
|
||||
case scExtensions:
|
||||
case scProcess:
|
||||
if (m_bExecuteProcess)
|
||||
if (executeProcess)
|
||||
{
|
||||
SchedulerScriptController::StartScript(pTask->m_szParam, pTask->m_eCommand == scProcess, pTask->m_iID);
|
||||
SchedulerScriptController::StartScript(task->m_param, task->m_command == scProcess, task->m_id);
|
||||
}
|
||||
break;
|
||||
|
||||
case scActivateServer:
|
||||
case scDeactivateServer:
|
||||
EditServer(pTask->m_eCommand == scActivateServer, pTask->m_szParam);
|
||||
EditServer(task->m_command == scActivateServer, task->m_param);
|
||||
break;
|
||||
|
||||
case scFetchFeed:
|
||||
if (m_bExecuteProcess)
|
||||
if (executeProcess)
|
||||
{
|
||||
FetchFeed(pTask->m_szParam);
|
||||
FetchFeed(task->m_param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -281,91 +242,88 @@ void Scheduler::ExecuteTask(Task* pTask)
|
||||
|
||||
void Scheduler::PrepareLog()
|
||||
{
|
||||
m_bDownloadRateChanged = false;
|
||||
m_bPauseDownloadChanged = false;
|
||||
m_bPausePostProcessChanged = false;
|
||||
m_bPauseScanChanged = false;
|
||||
m_bServerChanged = false;
|
||||
m_downloadRateChanged = false;
|
||||
m_pauseDownloadChanged = false;
|
||||
m_pausePostProcessChanged = false;
|
||||
m_pauseScanChanged = false;
|
||||
m_serverChanged = false;
|
||||
}
|
||||
|
||||
void Scheduler::PrintLog()
|
||||
{
|
||||
if (m_bDownloadRateChanged)
|
||||
if (m_downloadRateChanged)
|
||||
{
|
||||
info("Scheduler: setting download rate to %i KB/s", g_pOptions->GetDownloadRate() / 1024);
|
||||
info("Scheduler: setting download rate to %i KB/s", g_WorkState->GetSpeedLimit() / 1024);
|
||||
}
|
||||
if (m_bPauseDownloadChanged)
|
||||
if (m_pauseDownloadChanged)
|
||||
{
|
||||
info("Scheduler: %s download", g_pOptions->GetPauseDownload() ? "pausing" : "unpausing");
|
||||
info("Scheduler: %s download", g_WorkState->GetPauseDownload() ? "pausing" : "unpausing");
|
||||
}
|
||||
if (m_bPausePostProcessChanged)
|
||||
if (m_pausePostProcessChanged)
|
||||
{
|
||||
info("Scheduler: %s post-processing", g_pOptions->GetPausePostProcess() ? "pausing" : "unpausing");
|
||||
info("Scheduler: %s post-processing", g_WorkState->GetPausePostProcess() ? "pausing" : "unpausing");
|
||||
}
|
||||
if (m_bPauseScanChanged)
|
||||
if (m_pauseScanChanged)
|
||||
{
|
||||
info("Scheduler: %s scan", g_pOptions->GetPauseScan() ? "pausing" : "unpausing");
|
||||
info("Scheduler: %s scan", g_WorkState->GetPauseScan() ? "pausing" : "unpausing");
|
||||
}
|
||||
if (m_bServerChanged)
|
||||
if (m_serverChanged)
|
||||
{
|
||||
int index = 0;
|
||||
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++, index++)
|
||||
for (NewsServer* server : g_ServerPool->GetServers())
|
||||
{
|
||||
NewsServer* pServer = *it;
|
||||
if (pServer->GetActive() != m_ServerStatusList[index])
|
||||
if (server->GetActive() != m_serverStatusList[index])
|
||||
{
|
||||
info("Scheduler: %s %s", pServer->GetActive() ? "activating" : "deactivating", pServer->GetName());
|
||||
info("Scheduler: %s %s", server->GetActive() ? "activating" : "deactivating", server->GetName());
|
||||
}
|
||||
index++;
|
||||
}
|
||||
g_pServerPool->Changed();
|
||||
g_ServerPool->Changed();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::EditServer(bool bActive, const char* szServerList)
|
||||
void Scheduler::EditServer(bool active, const char* serverList)
|
||||
{
|
||||
Tokenizer tok(szServerList, ",;");
|
||||
while (const char* szServer = tok.Next())
|
||||
Tokenizer tok(serverList, ",;");
|
||||
while (const char* serverRef = tok.Next())
|
||||
{
|
||||
int iID = atoi(szServer);
|
||||
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
|
||||
int id = atoi(serverRef);
|
||||
for (NewsServer* server : g_ServerPool->GetServers())
|
||||
{
|
||||
NewsServer* pServer = *it;
|
||||
if ((iID > 0 && pServer->GetID() == iID) ||
|
||||
!strcasecmp(pServer->GetName(), szServer))
|
||||
if ((id > 0 && server->GetId() == id) ||
|
||||
!strcasecmp(server->GetName(), serverRef))
|
||||
{
|
||||
if (!m_bServerChanged)
|
||||
if (!m_serverChanged)
|
||||
{
|
||||
// store old server status for logging
|
||||
m_ServerStatusList.clear();
|
||||
m_ServerStatusList.reserve(g_pServerPool->GetServers()->size());
|
||||
for (Servers::iterator it2 = g_pServerPool->GetServers()->begin(); it2 != g_pServerPool->GetServers()->end(); it2++)
|
||||
m_serverStatusList.clear();
|
||||
m_serverStatusList.reserve(g_ServerPool->GetServers()->size());
|
||||
for (NewsServer* server2 : g_ServerPool->GetServers())
|
||||
{
|
||||
NewsServer* pServer2 = *it2;
|
||||
m_ServerStatusList.push_back(pServer2->GetActive());
|
||||
m_serverStatusList.push_back(server2->GetActive());
|
||||
}
|
||||
}
|
||||
m_bServerChanged = true;
|
||||
pServer->SetActive(bActive);
|
||||
m_serverChanged = true;
|
||||
server->SetActive(active);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::FetchFeed(const char* szFeedList)
|
||||
void Scheduler::FetchFeed(const char* feedList)
|
||||
{
|
||||
Tokenizer tok(szFeedList, ",;");
|
||||
while (const char* szFeed = tok.Next())
|
||||
Tokenizer tok(feedList, ",;");
|
||||
while (const char* feedRef = tok.Next())
|
||||
{
|
||||
int iID = atoi(szFeed);
|
||||
for (Feeds::iterator it = g_pFeedCoordinator->GetFeeds()->begin(); it != g_pFeedCoordinator->GetFeeds()->end(); it++)
|
||||
int id = atoi(feedRef);
|
||||
for (FeedInfo* feed : g_FeedCoordinator->GetFeeds())
|
||||
{
|
||||
FeedInfo* pFeed = *it;
|
||||
if (pFeed->GetID() == iID ||
|
||||
!strcasecmp(pFeed->GetName(), szFeed) ||
|
||||
!strcasecmp("0", szFeed))
|
||||
if (feed->GetId() == id ||
|
||||
!strcasecmp(feed->GetName(), feedRef) ||
|
||||
!strcasecmp("0", feedRef))
|
||||
{
|
||||
g_pFeedCoordinator->FetchFeed(!strcasecmp("0", szFeed) ? 0 : pFeed->GetID());
|
||||
g_FeedCoordinator->FetchFeed(!strcasecmp("0", feedRef) ? 0 : feed->GetId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -374,110 +332,14 @@ void Scheduler::FetchFeed(const char* szFeedList)
|
||||
|
||||
void Scheduler::CheckScheduledResume()
|
||||
{
|
||||
time_t tResumeTime = g_pOptions->GetResumeTime();
|
||||
time_t tCurrentTime = time(NULL);
|
||||
if (tResumeTime > 0 && tCurrentTime >= tResumeTime)
|
||||
time_t resumeTime = g_WorkState->GetResumeTime();
|
||||
time_t currentTime = Util::CurrentTime();
|
||||
if (resumeTime > 0 && currentTime >= resumeTime)
|
||||
{
|
||||
info("Autoresume");
|
||||
g_pOptions->SetResumeTime(0);
|
||||
g_pOptions->SetPauseDownload(false);
|
||||
g_pOptions->SetPausePostProcess(false);
|
||||
g_pOptions->SetPauseScan(false);
|
||||
g_WorkState->SetResumeTime(0);
|
||||
g_WorkState->SetPauseDownload(false);
|
||||
g_WorkState->SetPausePostProcess(false);
|
||||
g_WorkState->SetPauseScan(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SchedulerScriptController::~SchedulerScriptController()
|
||||
{
|
||||
free(m_szScript);
|
||||
}
|
||||
|
||||
void SchedulerScriptController::StartScript(const char* szParam, bool bExternalProcess, int iTaskID)
|
||||
{
|
||||
char** argv = NULL;
|
||||
if (bExternalProcess && !Util::SplitCommandLine(szParam, &argv))
|
||||
{
|
||||
error("Could not execute scheduled process-script, failed to parse command line: %s", szParam);
|
||||
return;
|
||||
}
|
||||
|
||||
SchedulerScriptController* pScriptController = new SchedulerScriptController();
|
||||
|
||||
pScriptController->m_bExternalProcess = bExternalProcess;
|
||||
pScriptController->m_szScript = strdup(szParam);
|
||||
pScriptController->m_iTaskID = iTaskID;
|
||||
|
||||
if (bExternalProcess)
|
||||
{
|
||||
pScriptController->SetScript(argv[0]);
|
||||
pScriptController->SetArgs((const char**)argv, true);
|
||||
}
|
||||
|
||||
pScriptController->SetAutoDestroy(true);
|
||||
|
||||
pScriptController->Start();
|
||||
}
|
||||
|
||||
void SchedulerScriptController::Run()
|
||||
{
|
||||
if (m_bExternalProcess)
|
||||
{
|
||||
ExecuteExternalProcess();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteScriptList(m_szScript);
|
||||
}
|
||||
}
|
||||
|
||||
void SchedulerScriptController::ExecuteScript(Options::Script* pScript)
|
||||
{
|
||||
if (!pScript->GetSchedulerScript())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID);
|
||||
|
||||
SetScript(pScript->GetLocation());
|
||||
SetArgs(NULL, false);
|
||||
|
||||
char szInfoName[1024];
|
||||
snprintf(szInfoName, 1024, "scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID);
|
||||
szInfoName[1024-1] = '\0';
|
||||
SetInfoName(szInfoName);
|
||||
|
||||
SetLogPrefix(pScript->GetDisplayName());
|
||||
PrepareParams(pScript->GetName());
|
||||
|
||||
Execute();
|
||||
|
||||
SetLogPrefix(NULL);
|
||||
}
|
||||
|
||||
void SchedulerScriptController::PrepareParams(const char* szScriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetIntEnvVar("NZBSP_TASKID", m_iTaskID);
|
||||
|
||||
PrepareEnvScript(NULL, szScriptName);
|
||||
}
|
||||
|
||||
void SchedulerScriptController::ExecuteExternalProcess()
|
||||
{
|
||||
info("Executing scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID);
|
||||
|
||||
char szInfoName[1024];
|
||||
snprintf(szInfoName, 1024, "scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID);
|
||||
szInfoName[1024-1] = '\0';
|
||||
SetInfoName(szInfoName);
|
||||
|
||||
char szLogPrefix[1024];
|
||||
strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 1024);
|
||||
szLogPrefix[1024-1] = '\0';
|
||||
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
|
||||
SetLogPrefix(szLogPrefix);
|
||||
|
||||
Execute();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2008-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,25 +14,18 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SCHEDULER_H
|
||||
#define SCHEDULER_H
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Thread.h"
|
||||
#include "Service.h"
|
||||
|
||||
class Scheduler
|
||||
class Scheduler : public Service
|
||||
{
|
||||
public:
|
||||
enum ECommand
|
||||
@@ -42,7 +35,7 @@ public:
|
||||
scPausePostProcess,
|
||||
scUnpausePostProcess,
|
||||
scDownloadRate,
|
||||
scScript,
|
||||
scExtensions,
|
||||
scProcess,
|
||||
scPauseScan,
|
||||
scUnpauseScan,
|
||||
@@ -53,52 +46,55 @@ public:
|
||||
|
||||
class Task
|
||||
{
|
||||
private:
|
||||
int m_iID;
|
||||
int m_iHours;
|
||||
int m_iMinutes;
|
||||
int m_iWeekDaysBits;
|
||||
ECommand m_eCommand;
|
||||
char* m_szParam;
|
||||
time_t m_tLastExecuted;
|
||||
|
||||
public:
|
||||
Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand,
|
||||
const char* szParam);
|
||||
~Task();
|
||||
friend class Scheduler;
|
||||
Task(int id, int hours, int minutes, int weekDaysBits, ECommand command,
|
||||
const char* param) :
|
||||
m_id(id), m_hours(hours), m_minutes(minutes),
|
||||
m_weekDaysBits(weekDaysBits), m_command(command), m_param(param) {}
|
||||
friend class Scheduler;
|
||||
static const int STARTUP_TASK = -1;
|
||||
private:
|
||||
int m_id;
|
||||
int m_hours;
|
||||
int m_minutes;
|
||||
int m_weekDaysBits;
|
||||
ECommand m_command;
|
||||
CString m_param;
|
||||
time_t m_lastExecuted = 0;
|
||||
};
|
||||
|
||||
void AddTask(std::unique_ptr<Task> task);
|
||||
|
||||
protected:
|
||||
virtual int ServiceInterval() { return m_serviceInterval; }
|
||||
virtual void ServiceWork();
|
||||
|
||||
private:
|
||||
typedef std::vector<std::unique_ptr<Task>> TaskList;
|
||||
typedef std::vector<bool> ServerStatusList;
|
||||
|
||||
typedef std::list<Task*> TaskList;
|
||||
typedef std::vector<bool> ServerStatusList;
|
||||
TaskList m_taskList;
|
||||
Mutex m_taskListMutex;
|
||||
time_t m_lastCheck = 0;
|
||||
bool m_downloadRateChanged;
|
||||
bool m_executeProcess;
|
||||
bool m_pauseDownloadChanged;
|
||||
bool m_pausePostProcessChanged;
|
||||
bool m_pauseScanChanged;
|
||||
bool m_serverChanged;
|
||||
ServerStatusList m_serverStatusList;
|
||||
bool m_firstChecked = false;
|
||||
int m_serviceInterval = 1;
|
||||
|
||||
TaskList m_TaskList;
|
||||
Mutex m_mutexTaskList;
|
||||
time_t m_tLastCheck;
|
||||
bool m_bDownloadRateChanged;
|
||||
bool m_bExecuteProcess;
|
||||
bool m_bPauseDownloadChanged;
|
||||
bool m_bPausePostProcessChanged;
|
||||
bool m_bPauseScanChanged;
|
||||
bool m_bServerChanged;
|
||||
ServerStatusList m_ServerStatusList;
|
||||
void ExecuteTask(Task* pTask);
|
||||
void CheckTasks();
|
||||
static bool CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2);
|
||||
void PrepareLog();
|
||||
void PrintLog();
|
||||
void EditServer(bool bActive, const char* szServerList);
|
||||
void FetchFeed(const char* szFeedList);
|
||||
void CheckScheduledResume();
|
||||
|
||||
public:
|
||||
Scheduler();
|
||||
~Scheduler();
|
||||
void AddTask(Task* pTask);
|
||||
void FirstCheck();
|
||||
void IntervalCheck();
|
||||
void ExecuteTask(Task* task);
|
||||
void CheckTasks();
|
||||
void PrepareLog();
|
||||
void PrintLog();
|
||||
void EditServer(bool active, const char* serverList);
|
||||
void FetchFeed(const char* feedList);
|
||||
void CheckScheduledResume();
|
||||
void FirstCheck();
|
||||
void ScheduleNextWork();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
151
daemon/main/StackTrace.cpp
Executable file → Normal file
151
daemon/main/StackTrace.cpp
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,76 +14,44 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <dbghelp.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Log.h"
|
||||
#include "Options.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
extern void ExitProc();
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
void PrintBacktrace(PCONTEXT pContext)
|
||||
void PrintBacktrace(PCONTEXT context)
|
||||
{
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
|
||||
char szAppDir[MAX_PATH + 1];
|
||||
GetModuleFileName(NULL, szAppDir, sizeof(szAppDir));
|
||||
char* end = strrchr(szAppDir, PATH_SEPARATOR);
|
||||
char appDir[MAX_PATH + 1];
|
||||
GetModuleFileName(nullptr, appDir, sizeof(appDir));
|
||||
char* end = strrchr(appDir, PATH_SEPARATOR);
|
||||
if (end) *end = '\0';
|
||||
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS);
|
||||
|
||||
if (!SymInitialize(hProcess, szAppDir, TRUE))
|
||||
if (!SymInitialize(hProcess, appDir, TRUE))
|
||||
{
|
||||
warn("Could not obtain detailed exception information: SymInitialize failed");
|
||||
return;
|
||||
}
|
||||
|
||||
const int MAX_NAMELEN = 1024;
|
||||
IMAGEHLP_SYMBOL64* pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
|
||||
memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
|
||||
pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
||||
pSym->MaxNameLength = MAX_NAMELEN;
|
||||
IMAGEHLP_SYMBOL64* sym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
|
||||
memset(sym, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
|
||||
sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
||||
sym->MaxNameLength = MAX_NAMELEN;
|
||||
|
||||
IMAGEHLP_LINE64 ilLine;
|
||||
memset(&ilLine, 0, sizeof(ilLine));
|
||||
@@ -94,19 +62,19 @@ void PrintBacktrace(PCONTEXT pContext)
|
||||
DWORD imageType;
|
||||
#ifdef _M_IX86
|
||||
imageType = IMAGE_FILE_MACHINE_I386;
|
||||
sfStackFrame.AddrPC.Offset = pContext->Eip;
|
||||
sfStackFrame.AddrPC.Offset = context->Eip;
|
||||
sfStackFrame.AddrPC.Mode = AddrModeFlat;
|
||||
sfStackFrame.AddrFrame.Offset = pContext->Ebp;
|
||||
sfStackFrame.AddrFrame.Offset = context->Ebp;
|
||||
sfStackFrame.AddrFrame.Mode = AddrModeFlat;
|
||||
sfStackFrame.AddrStack.Offset = pContext->Esp;
|
||||
sfStackFrame.AddrStack.Offset = context->Esp;
|
||||
sfStackFrame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif _M_X64
|
||||
imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||
sfStackFrame.AddrPC.Offset = pContext->Rip;
|
||||
sfStackFrame.AddrPC.Offset = context->Rip;
|
||||
sfStackFrame.AddrPC.Mode = AddrModeFlat;
|
||||
sfStackFrame.AddrFrame.Offset = pContext->Rsp;
|
||||
sfStackFrame.AddrFrame.Offset = context->Rsp;
|
||||
sfStackFrame.AddrFrame.Mode = AddrModeFlat;
|
||||
sfStackFrame.AddrStack.Offset = pContext->Rsp;
|
||||
sfStackFrame.AddrStack.Offset = context->Rsp;
|
||||
sfStackFrame.AddrStack.Mode = AddrModeFlat;
|
||||
#else
|
||||
warn("Could not obtain detailed exception information: platform not supported");
|
||||
@@ -121,47 +89,46 @@ void PrintBacktrace(PCONTEXT pContext)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StackWalk64(imageType, hProcess, hThread, &sfStackFrame, pContext, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
|
||||
if (!StackWalk64(imageType, hProcess, hThread, &sfStackFrame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
|
||||
{
|
||||
warn("Could not obtain detailed exception information: StackWalk64 failed");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD64 dwAddr = sfStackFrame.AddrPC.Offset;
|
||||
char szSymName[1024];
|
||||
char szSrcFileName[1024];
|
||||
int iLineNumber = 0;
|
||||
BString<1024> symName;
|
||||
BString<1024> srcFileName;
|
||||
int lineNumber = 0;
|
||||
|
||||
DWORD64 dwSymbolDisplacement;
|
||||
if (SymGetSymFromAddr64(hProcess, dwAddr, &dwSymbolDisplacement, pSym))
|
||||
if (SymGetSymFromAddr64(hProcess, dwAddr, &dwSymbolDisplacement, sym))
|
||||
{
|
||||
UnDecorateSymbolName(pSym->Name, szSymName, sizeof(szSymName), UNDNAME_COMPLETE);
|
||||
szSymName[sizeof(szSymName) - 1] = '\0';
|
||||
UnDecorateSymbolName(sym->Name, symName, symName.Capacity(), UNDNAME_COMPLETE);
|
||||
symName[sizeof(symName) - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(szSymName, "<symbol not available>", sizeof(szSymName));
|
||||
symName = "<symbol not available>";
|
||||
}
|
||||
|
||||
DWORD dwLineDisplacement;
|
||||
if (SymGetLineFromAddr64(hProcess, dwAddr, &dwLineDisplacement, &ilLine))
|
||||
{
|
||||
iLineNumber = ilLine.LineNumber;
|
||||
char* szUseFileName = ilLine.FileName;
|
||||
char* szRoot = strstr(szUseFileName, "\\daemon\\");
|
||||
if (szRoot)
|
||||
lineNumber = ilLine.LineNumber;
|
||||
char* useFileName = ilLine.FileName;
|
||||
char* root = strstr(useFileName, "\\daemon\\");
|
||||
if (root)
|
||||
{
|
||||
szUseFileName = szRoot;
|
||||
useFileName = root;
|
||||
}
|
||||
strncpy(szSrcFileName, szUseFileName, sizeof(szSrcFileName));
|
||||
szSrcFileName[sizeof(szSrcFileName) - 1] = '\0';
|
||||
srcFileName = useFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(szSrcFileName, "<filename not available>", sizeof(szSymName));
|
||||
srcFileName = "<filename not available>";
|
||||
}
|
||||
|
||||
info("%s (%i) : %s", szSrcFileName, iLineNumber, szSymName);
|
||||
info("%s (%i) : %s", *srcFileName, lineNumber, *symName);
|
||||
|
||||
if (sfStackFrame.AddrReturn.Offset == 0)
|
||||
{
|
||||
@@ -171,26 +138,29 @@ void PrintBacktrace(PCONTEXT pContext)
|
||||
}
|
||||
#endif
|
||||
|
||||
LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
|
||||
LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* exPtrs)
|
||||
{
|
||||
error("Unhandled Exception: code: 0x%8.8X, flags: %d, address: 0x%8.8X",
|
||||
pExPtrs->ExceptionRecord->ExceptionCode,
|
||||
pExPtrs->ExceptionRecord->ExceptionFlags,
|
||||
pExPtrs->ExceptionRecord->ExceptionAddress);
|
||||
exPtrs->ExceptionRecord->ExceptionCode,
|
||||
exPtrs->ExceptionRecord->ExceptionFlags,
|
||||
exPtrs->ExceptionRecord->ExceptionAddress);
|
||||
|
||||
#ifdef DEBUG
|
||||
PrintBacktrace(pExPtrs->ContextRecord);
|
||||
#else
|
||||
info("Detailed exception information can be printed by debug version of NZBGet (available from download page)");
|
||||
#endif
|
||||
|
||||
ExitProcess(-1);
|
||||
PrintBacktrace(exPtrs->ContextRecord);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
#else
|
||||
info("Detailed crash information can be printed by debug version of NZBGet."
|
||||
" For more info visit http://nzbget.net/crash-dump");
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
#endif
|
||||
}
|
||||
|
||||
void InstallErrorHandler()
|
||||
{
|
||||
SetUnhandledExceptionFilter(ExceptionFilter);
|
||||
if (g_Options->GetCrashTrace())
|
||||
{
|
||||
SetUnhandledExceptionFilter(ExceptionFilter);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -204,7 +174,7 @@ std::vector<sighandler> SignalProcList;
|
||||
/**
|
||||
* activates the creation of core-files
|
||||
*/
|
||||
void EnableDumpCore()
|
||||
void EnableCoreDump()
|
||||
{
|
||||
rlimit rlim;
|
||||
rlim.rlim_cur= RLIM_INFINITY;
|
||||
@@ -218,7 +188,7 @@ void PrintBacktrace()
|
||||
{
|
||||
#ifdef HAVE_BACKTRACE
|
||||
printf("Segmentation fault, tracing...\n");
|
||||
|
||||
|
||||
void *array[100];
|
||||
size_t size;
|
||||
char **strings;
|
||||
@@ -251,9 +221,9 @@ void PrintBacktrace()
|
||||
/*
|
||||
* Signal handler
|
||||
*/
|
||||
void SignalProc(int iSignal)
|
||||
void SignalProc(int signum)
|
||||
{
|
||||
switch (iSignal)
|
||||
switch (signum)
|
||||
{
|
||||
case SIGINT:
|
||||
signal(SIGINT, SIG_DFL); // Reset the signal handler
|
||||
@@ -281,9 +251,9 @@ void SignalProc(int iSignal)
|
||||
void InstallErrorHandler()
|
||||
{
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
if (g_pOptions->GetDumpCore())
|
||||
if (g_Options->GetCrashDump())
|
||||
{
|
||||
EnableDumpCore();
|
||||
EnableCoreDump();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -291,11 +261,14 @@ void InstallErrorHandler()
|
||||
signal(SIGTERM, SignalProc);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#ifdef DEBUG
|
||||
signal(SIGSEGV, SignalProc);
|
||||
if (g_Options->GetCrashTrace())
|
||||
{
|
||||
signal(SIGSEGV, SignalProc);
|
||||
}
|
||||
#endif
|
||||
#ifdef SIGCHLD_HANDLER
|
||||
// it could be necessary on some systems to activate a handler for SIGCHLD
|
||||
// however it make troubles on other systems and is deactivated by default
|
||||
// it could be necessary on some systems to activate a handler for SIGCHLD
|
||||
// however it make troubles on other systems and is deactivated by default
|
||||
signal(SIGCHLD, SignalProc);
|
||||
#endif
|
||||
}
|
||||
@@ -308,8 +281,8 @@ class SegFault
|
||||
public:
|
||||
void DoSegFault()
|
||||
{
|
||||
char* N = NULL;
|
||||
strcpy(N, "");
|
||||
char* N = nullptr;
|
||||
*N = '\0';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
11
daemon/main/StackTrace.h
Executable file → Normal file
11
daemon/main/StackTrace.h
Executable file → Normal file
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,12 +14,7 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
27
daemon/main/WorkState.cpp
Normal file
27
daemon/main/WorkState.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "WorkState.h"
|
||||
|
||||
void WorkState::Changed()
|
||||
{
|
||||
Notify(nullptr);
|
||||
}
|
||||
76
daemon/main/WorkState.h
Normal file
76
daemon/main/WorkState.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef WORKSTATE_H
|
||||
#define WORKSTATE_H
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
// WorkState is observable but notifications are not 100% reliable.
|
||||
// The changes via Set-methods and readings via Get-methods are not synchronized throughout the program.
|
||||
// As result race conditions may occur and some changes may go unnoticed.
|
||||
// When waiting for changes don't wait too long to avoid lock ups.
|
||||
|
||||
class WorkState : public Subject
|
||||
{
|
||||
public:
|
||||
void SetPauseDownload(bool pauseDownload) { m_pauseDownload = pauseDownload; Changed(); }
|
||||
bool GetPauseDownload() const { return m_pauseDownload; }
|
||||
void SetPausePostProcess(bool pausePostProcess) { m_pausePostProcess = pausePostProcess; Changed(); }
|
||||
bool GetPausePostProcess() const { return m_pausePostProcess; }
|
||||
void SetPauseScan(bool pauseScan) { m_pauseScan = pauseScan; Changed(); }
|
||||
bool GetPauseScan() const { return m_pauseScan; }
|
||||
void SetTempPauseDownload(bool tempPauseDownload) { m_tempPauseDownload = tempPauseDownload; Changed(); }
|
||||
bool GetTempPauseDownload() const { return m_tempPauseDownload; }
|
||||
void SetTempPausePostprocess(bool tempPausePostprocess) { m_tempPausePostprocess = tempPausePostprocess; Changed(); }
|
||||
bool GetTempPausePostprocess() const { return m_tempPausePostprocess; }
|
||||
void SetPauseFrontend(bool pauseFrontend) { m_pauseFrontend = pauseFrontend; Changed(); }
|
||||
bool GetPauseFrontend() const { return m_pauseFrontend; }
|
||||
void SetSpeedLimit(int speedLimit) { m_speedLimit = speedLimit; Changed(); }
|
||||
int GetSpeedLimit() const { return m_speedLimit; }
|
||||
void SetResumeTime(time_t resumeTime) { m_resumeTime = resumeTime; Changed(); }
|
||||
time_t GetResumeTime() const { return m_resumeTime; }
|
||||
void SetLocalTimeOffset(int localTimeOffset) { m_localTimeOffset = localTimeOffset; Changed(); }
|
||||
int GetLocalTimeOffset() { return m_localTimeOffset; }
|
||||
void SetQuotaReached(bool quotaReached) { m_quotaReached = quotaReached; Changed(); }
|
||||
bool GetQuotaReached() { return m_quotaReached; }
|
||||
void SetDownloading(bool downloading) { m_downloading = downloading; Changed(); }
|
||||
bool GetDownloading() { return m_downloading; }
|
||||
|
||||
private:
|
||||
bool m_pauseDownload = false;
|
||||
bool m_pausePostProcess = false;
|
||||
bool m_pauseScan = false;
|
||||
bool m_tempPauseDownload = true;
|
||||
bool m_tempPausePostprocess = true;
|
||||
bool m_pauseFrontend = false;
|
||||
int m_downloadRate = 0;
|
||||
time_t m_resumeTime = 0;
|
||||
int m_localTimeOffset = 0;
|
||||
bool m_quotaReached = false;
|
||||
int m_speedLimit = 0;
|
||||
bool m_downloading = false;
|
||||
|
||||
void Changed();
|
||||
};
|
||||
|
||||
extern WorkState* g_WorkState;
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,21 +14,278 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NZBGET_H
|
||||
#define NZBGET_H
|
||||
|
||||
/***************** DEFINES FOR WINDOWS *****************/
|
||||
#ifdef WIN32
|
||||
|
||||
// WIN32
|
||||
/* Define to 1 to not use curses */
|
||||
//#define DISABLE_CURSES
|
||||
|
||||
/* Define to 1 to disable smart par-verification and restoration */
|
||||
//#define DISABLE_PARCHECK
|
||||
|
||||
/* Define to 1 to disable TLS/SSL-support. */
|
||||
//#define DISABLE_TLS
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
/* Define to 1 to use OpenSSL library for TLS/SSL-support */
|
||||
#define HAVE_OPENSSL
|
||||
/* Define to 1 to use GnuTLS library for TLS/SSL-support */
|
||||
//#define HAVE_LIBGNUTLS
|
||||
#endif
|
||||
|
||||
/* Define to 1 if OpenSSL supports function "X509_check_host". */
|
||||
#define HAVE_X509_CHECK_HOST 1
|
||||
|
||||
/* Define to the name of macro which returns the name of function being
|
||||
compiled */
|
||||
#define FUNCTION_MACRO_NAME __FUNCTION__
|
||||
|
||||
/* Define to 1 if ctime_r takes 2 arguments */
|
||||
#undef HAVE_CTIME_R_2
|
||||
|
||||
/* Define to 1 if ctime_r takes 3 arguments */
|
||||
#define HAVE_CTIME_R_3
|
||||
|
||||
/* Define to 1 if getopt_long is supported */
|
||||
#undef HAVE_GETOPT_LONG
|
||||
|
||||
/* Define to 1 if variadic macros are supported */
|
||||
#define HAVE_VARIADIC_MACROS
|
||||
|
||||
/* Define to 1 if function GetAddrInfo is supported */
|
||||
#define HAVE_GETADDRINFO
|
||||
|
||||
/* Determine what socket length (socklen_t) data type is */
|
||||
#define SOCKLEN_T socklen_t
|
||||
|
||||
/* Define to 1 if you have the <regex.h> header file. */
|
||||
#ifndef DISABLE_REGEX
|
||||
#define HAVE_REGEX_H 1
|
||||
// Static linking to regex library
|
||||
#define REGEX_STATIC
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GZIP
|
||||
// Static linking to zlib library
|
||||
#define ZLIB_WINAPI
|
||||
#endif
|
||||
|
||||
/* Suppress warnings */
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
|
||||
/* Suppress warnings */
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
|
||||
#ifndef _WIN64
|
||||
#define _USE_32BIT_TIME_T
|
||||
#endif
|
||||
|
||||
#if _WIN32_WINNT < 0x0501
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#endif
|
||||
|
||||
#ifdef _WIN64
|
||||
#define __amd64__
|
||||
#else
|
||||
#define __i686__
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
// detection of memory leaks
|
||||
#define _CRTDBG_MAP_ALLOC
|
||||
#endif
|
||||
|
||||
#pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning)
|
||||
#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/***************** GLOBAL INCLUDES *****************/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// WINDOWS INCLUDES
|
||||
|
||||
// Using "WIN32_LEAN_AND_MEAN" to disable including of many unneeded headers
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <winsvc.h>
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#include <dbghelp.h>
|
||||
#include <mmsystem.h>
|
||||
#include <io.h>
|
||||
#include <process.h>
|
||||
#include <WinIoCtl.h>
|
||||
#include <wincon.h>
|
||||
#include <shellapi.h>
|
||||
#include <winreg.h>
|
||||
|
||||
#include <comutil.h>
|
||||
#import <msxml.tlb> named_guids
|
||||
using namespace MSXML;
|
||||
|
||||
#if _MSC_VER >= 1600
|
||||
#include <stdint.h>
|
||||
#define HAVE_STDINT_H
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include <crtdbg.h>
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
// POSIX INCLUDES
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <signal.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/file.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#ifndef DISABLE_LIBXML2
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <libxml/xmlerror.h>
|
||||
#include <libxml/entities.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ENDIAN_H
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#endif /* POSIX INCLUDES */
|
||||
|
||||
// COMMON INCLUDES
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
|
||||
// NOTE: do not include <iostream> in "nzbget.h". <iostream> contains objects requiring
|
||||
// intialization, causing every unit in nzbget to have initialization routine. This in particular
|
||||
// is causing fatal problems in SIMD units which must not have static initialization because
|
||||
// they contain code with runtime CPU dispatching.
|
||||
//#include <iostream>
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#ifdef WIN32
|
||||
#include <BaseTsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
typedef int pid_t;
|
||||
#endif
|
||||
#include <gnutls/gnutls.h>
|
||||
#include <gnutls/x509.h>
|
||||
#if GNUTLS_VERSION_NUMBER <= 0x020b00
|
||||
#define NEED_GCRYPT_LOCKING
|
||||
#endif
|
||||
#ifdef NEED_GCRYPT_LOCKING
|
||||
#include <gcrypt.h>
|
||||
#endif /* NEED_GCRYPT_LOCKING */
|
||||
#include <nettle/sha.h>
|
||||
#include <nettle/pbkdf2.h>
|
||||
#include <nettle/aes.h>
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/comp.h>
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
#ifdef HAVE_REGEX_H
|
||||
#include <regex.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GZIP
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
#include <assert.h>
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
#ifdef HAVE_MEMORY_H
|
||||
# include <memory.h>
|
||||
#endif
|
||||
#endif /* NOT DISABLE_PARCHECK */
|
||||
|
||||
|
||||
/***************** GLOBAL FUNCTION AND CONST OVERRIDES *****************/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// WINDOWS
|
||||
|
||||
#define snprintf _snprintf
|
||||
#ifndef strdup
|
||||
@@ -39,39 +296,50 @@
|
||||
#define gmtime_r(time, tm) gmtime_s(tm, time)
|
||||
#define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr)
|
||||
#define strerror_r(errnum, buffer, size) strerror_s(buffer, size, errnum)
|
||||
#define int32_t __int32
|
||||
#define mkdir(dir, flags) _mkdir(dir)
|
||||
#define rmdir _rmdir
|
||||
#define strcasecmp(a, b) _stricmp(a, b)
|
||||
#define strncasecmp(a, b, c) _strnicmp(a, b, c)
|
||||
#define ssize_t SSIZE_T
|
||||
#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
|
||||
#define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR)
|
||||
#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)
|
||||
#define S_DIRMODE NULL
|
||||
#define usleep(usec) Sleep((usec) / 1000)
|
||||
#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
|
||||
#define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR)
|
||||
#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)
|
||||
#define S_DIRMODE nullptr
|
||||
#define socklen_t int
|
||||
#define SHUT_WR 0x01
|
||||
#define SHUT_RDWR 0x02
|
||||
#define PATH_SEPARATOR '\\'
|
||||
#define ALT_PATH_SEPARATOR '/'
|
||||
#define LINE_ENDING "\r\n"
|
||||
#define pid_t int
|
||||
#define atoll _atoi64
|
||||
#define fseek _fseeki64
|
||||
#define ftell _ftelli64
|
||||
// va_copy is available in vc2013 and onwards
|
||||
#if _MSC_VER < 1800
|
||||
#define va_copy(d,s) ((d) = (s))
|
||||
#endif
|
||||
#ifndef FSCTL_SET_SPARSE
|
||||
#define FSCTL_SET_SPARSE 590020
|
||||
#endif
|
||||
#define FOPEN_RB "rbN"
|
||||
#define FOPEN_RBP "rb+N"
|
||||
#define FOPEN_WB "wbN"
|
||||
#define FOPEN_WBP "wb+N"
|
||||
#define FOPEN_AB "abN"
|
||||
#define FOPEN_ABP "ab+N"
|
||||
|
||||
#pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning)
|
||||
#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
|
||||
#define __SSE2__
|
||||
#define __SSSE3__
|
||||
#define __PCLMUL__
|
||||
|
||||
#ifdef DEBUG
|
||||
// redefine "exit" to avoid printing memory leaks report when terminated because of wrong command line switches
|
||||
#define exit(code) ExitProcess(code)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
FILE _iob[] = {*stdin, *stdout, *stderr};
|
||||
extern "C" FILE * __cdecl __iob_func(void) { return _iob; }
|
||||
// For static linking of OpenSSL libraries:
|
||||
#pragma comment (lib, "legacy_stdio_definitions.lib")
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
#else
|
||||
|
||||
@@ -88,14 +356,74 @@
|
||||
#define FOPEN_RB "rb"
|
||||
#define FOPEN_RBP "rb+"
|
||||
#define FOPEN_WB "wb"
|
||||
#define FOPEN_WBP "wb+"
|
||||
#define FOPEN_AB "ab"
|
||||
#define FOPEN_ABP "ab+"
|
||||
#define CHILD_WATCHDOG 1
|
||||
|
||||
#endif
|
||||
#endif /* POSIX */
|
||||
|
||||
// COMMON DEFINES FOR ALL PLATFORMS
|
||||
#ifndef SHUT_RDWR
|
||||
#define SHUT_RDWR 2
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STDINT_H
|
||||
typedef uint8_t uint8;
|
||||
typedef int16_t int16;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t int32;
|
||||
typedef uint32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef uint64_t uint64;
|
||||
#else
|
||||
typedef unsigned char uint8;
|
||||
typedef signed short int16;
|
||||
typedef unsigned short uint16;
|
||||
typedef signed int int32;
|
||||
typedef unsigned int uint32;
|
||||
typedef signed long long int64;
|
||||
typedef unsigned long long uint64;
|
||||
#endif
|
||||
|
||||
#ifndef PRId64
|
||||
#define PRId64 "lld"
|
||||
#endif
|
||||
#ifndef PRIi64
|
||||
#define PRIi64 "lli"
|
||||
#endif
|
||||
#ifndef PRIu64
|
||||
#define PRIu64 "llu"
|
||||
#endif
|
||||
|
||||
typedef unsigned char uchar;
|
||||
|
||||
// Assume little endian if byte order is not defined
|
||||
#ifndef __BYTE_ORDER
|
||||
#define __LITTLE_ENDIAN 1234
|
||||
#define __BIG_ENDIAN 4321
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define PRINTF_SYNTAX(strindex) __attribute__ ((format (printf, strindex, strindex+1)))
|
||||
#define SCANF_SYNTAX(strindex) __attribute__ ((format (scanf, strindex, strindex+1)))
|
||||
#else
|
||||
#define PRINTF_SYNTAX(strindex)
|
||||
#define SCANF_SYNTAX(strindex)
|
||||
#endif
|
||||
|
||||
// providing "std::make_unique" for GCC 4.8.x (only 4.8.x)
|
||||
#if __GNUC__ && __cplusplus < 201402L && __cpp_generic_lambdas < 201304
|
||||
namespace std {
|
||||
template<class T> struct _Unique_if { typedef unique_ptr<T> _Single_object; };
|
||||
template<class T> struct _Unique_if<T[]> { typedef unique_ptr<T[]> _Unknown_bound; };
|
||||
template<class T, class... Args> typename _Unique_if<T>::_Single_object make_unique(Args&&... args) {
|
||||
return unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
template<class T> typename _Unique_if<T>::_Unknown_bound make_unique(size_t n) {
|
||||
typedef typename remove_extent<T>::type U;
|
||||
return unique_ptr<T>(new U[n]());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NZBGET_H */
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,76 +15,37 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifdef WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "ArticleDownloader.h"
|
||||
#include "ArticleWriter.h"
|
||||
#include "Decoder.h"
|
||||
#include "Log.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "ServerPool.h"
|
||||
#include "StatMeter.h"
|
||||
#include "Util.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
extern ServerPool* g_pServerPool;
|
||||
extern StatMeter* g_pStatMeter;
|
||||
|
||||
ArticleDownloader::ArticleDownloader()
|
||||
{
|
||||
debug("Creating ArticleDownloader");
|
||||
|
||||
m_szInfoName = NULL;
|
||||
m_szConnectionName[0] = '\0';
|
||||
m_pConnection = NULL;
|
||||
m_eStatus = adUndefined;
|
||||
m_eFormat = Decoder::efUnknown;
|
||||
m_szArticleFilename = NULL;
|
||||
m_iDownloadedSize = 0;
|
||||
m_ArticleWriter.SetOwner(this);
|
||||
SetLastUpdateTimeNow();
|
||||
}
|
||||
|
||||
ArticleDownloader::~ArticleDownloader()
|
||||
{
|
||||
debug("Destroying ArticleDownloader");
|
||||
|
||||
free(m_szInfoName);
|
||||
free(m_szArticleFilename);
|
||||
}
|
||||
|
||||
void ArticleDownloader::SetInfoName(const char* szInfoName)
|
||||
void ArticleDownloader::SetInfoName(const char* infoName)
|
||||
{
|
||||
m_szInfoName = strdup(szInfoName);
|
||||
m_ArticleWriter.SetInfoName(m_szInfoName);
|
||||
m_infoName = infoName;
|
||||
m_articleWriter.SetInfoName(m_infoName);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -102,8 +63,8 @@ void ArticleDownloader::SetInfoName(const char* szInfoName)
|
||||
- if download fails with error "Not-Found" (article or group not found) or with CRC error,
|
||||
add the server to failed server list;
|
||||
- if download fails with general failure error (article incomplete, other unknown error
|
||||
codes), try the same server again as many times as defined by option <Retries>; if all attempts
|
||||
fail, add the server to failed server list;
|
||||
codes), try the same server again as many times as defined by option <ArticleRetries>;
|
||||
if all attempts fail, add the server to failed server list;
|
||||
- if all servers from current level were tried, increase level;
|
||||
- if all servers from all levels were tried, break the loop with failure status.
|
||||
<end-loop>
|
||||
@@ -114,610 +75,518 @@ void ArticleDownloader::Run()
|
||||
|
||||
SetStatus(adRunning);
|
||||
|
||||
m_ArticleWriter.SetFileInfo(m_pFileInfo);
|
||||
m_ArticleWriter.SetArticleInfo(m_pArticleInfo);
|
||||
m_ArticleWriter.Prepare();
|
||||
m_articleWriter.SetFileInfo(m_fileInfo);
|
||||
m_articleWriter.SetArticleInfo(m_articleInfo);
|
||||
m_articleWriter.Prepare();
|
||||
|
||||
EStatus Status = adFailed;
|
||||
int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
|
||||
int iRemainedRetries = iRetries;
|
||||
Servers failedServers;
|
||||
failedServers.reserve(g_pServerPool->GetServers()->size());
|
||||
NewsServer* pWantServer = NULL;
|
||||
NewsServer* pLastServer = NULL;
|
||||
int iLevel = 0;
|
||||
int iServerConfigGeneration = g_pServerPool->GetGeneration();
|
||||
bool bForce = m_pFileInfo->GetNZBInfo()->GetForcePriority();
|
||||
EStatus status = adFailed;
|
||||
int retries = g_Options->GetArticleRetries() > 0 ? g_Options->GetArticleRetries() : 1;
|
||||
int remainedRetries = retries;
|
||||
ServerPool::RawServerList failedServers;
|
||||
failedServers.reserve(g_ServerPool->GetServers()->size());
|
||||
NewsServer* wantServer = nullptr;
|
||||
NewsServer* lastServer = nullptr;
|
||||
int level = 0;
|
||||
int serverConfigGeneration = g_ServerPool->GetGeneration();
|
||||
bool force = m_fileInfo->GetNzbInfo()->GetForcePriority();
|
||||
|
||||
while (!IsStopped())
|
||||
{
|
||||
Status = adFailed;
|
||||
status = adFailed;
|
||||
|
||||
SetStatus(adWaiting);
|
||||
while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration()))
|
||||
while (!m_connection && !(IsStopped() || serverConfigGeneration != g_ServerPool->GetGeneration()))
|
||||
{
|
||||
m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers);
|
||||
usleep(5 * 1000);
|
||||
m_connection = g_ServerPool->GetConnection(level, wantServer, &failedServers);
|
||||
Util::Sleep(5);
|
||||
}
|
||||
SetLastUpdateTimeNow();
|
||||
SetStatus(adRunning);
|
||||
|
||||
if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
|
||||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
|
||||
iServerConfigGeneration != g_pServerPool->GetGeneration())
|
||||
if (IsStopped() || ((g_WorkState->GetPauseDownload() || g_WorkState->GetQuotaReached()) && !force) ||
|
||||
(g_WorkState->GetTempPauseDownload() && !m_fileInfo->GetExtraPriority()) ||
|
||||
serverConfigGeneration != g_ServerPool->GetGeneration())
|
||||
{
|
||||
Status = adRetry;
|
||||
status = adRetry;
|
||||
break;
|
||||
}
|
||||
|
||||
pLastServer = m_pConnection->GetNewsServer();
|
||||
lastServer = m_connection->GetNewsServer();
|
||||
level = lastServer->GetNormLevel();
|
||||
|
||||
m_pConnection->SetSuppressErrors(false);
|
||||
m_connection->SetSuppressErrors(false);
|
||||
|
||||
snprintf(m_szConnectionName, sizeof(m_szConnectionName), "%s (%s)",
|
||||
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost());
|
||||
m_szConnectionName[sizeof(m_szConnectionName) - 1] = '\0';
|
||||
m_connectionName.Format("%s (%s)",
|
||||
m_connection->GetNewsServer()->GetName(), m_connection->GetHost());
|
||||
|
||||
// check server retention
|
||||
bool retentionFailure = m_connection->GetNewsServer()->GetRetention() > 0 &&
|
||||
(Util::CurrentTime() - m_fileInfo->GetTime()) / 86400 > m_connection->GetNewsServer()->GetRetention();
|
||||
if (retentionFailure)
|
||||
{
|
||||
detail("Article %s @ %s failed: out of server retention (file age: %i, configured retention: %i)",
|
||||
*m_infoName, *m_connectionName,
|
||||
(int)(Util::CurrentTime() - m_fileInfo->GetTime()) / 86400,
|
||||
m_connection->GetNewsServer()->GetRetention());
|
||||
status = adFailed;
|
||||
FreeConnection(true);
|
||||
}
|
||||
|
||||
if (m_connection && !IsStopped())
|
||||
{
|
||||
detail("Downloading %s @ %s", *m_infoName, *m_connectionName);
|
||||
}
|
||||
|
||||
// test connection
|
||||
bool bConnected = m_pConnection && m_pConnection->Connect();
|
||||
if (bConnected && !IsStopped())
|
||||
bool connected = m_connection && m_connection->Connect();
|
||||
if (connected && !IsStopped())
|
||||
{
|
||||
NewsServer* pNewsServer = m_pConnection->GetNewsServer();
|
||||
detail("Downloading %s @ %s", m_szInfoName, m_szConnectionName);
|
||||
NewsServer* newsServer = m_connection->GetNewsServer();
|
||||
|
||||
Status = Download();
|
||||
// Download article
|
||||
status = Download();
|
||||
|
||||
if (Status == adFinished || Status == adFailed || Status == adNotFound || Status == adCrcError)
|
||||
if (status == adFinished || status == adFailed || status == adNotFound || status == adCrcError)
|
||||
{
|
||||
m_ServerStats.StatOp(pNewsServer->GetID(), Status == adFinished ? 1 : 0, Status == adFinished ? 0 : 1, ServerStatList::soSet);
|
||||
m_serverStats.StatOp(newsServer->GetId(), status == adFinished ? 1 : 0, status == adFinished ? 0 : 1, ServerStatList::soSet);
|
||||
}
|
||||
}
|
||||
|
||||
if (bConnected)
|
||||
{
|
||||
if (Status == adConnectError)
|
||||
{
|
||||
m_pConnection->Disconnect();
|
||||
bConnected = false;
|
||||
Status = adFailed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// freeing connection allows other threads to start.
|
||||
// we doing this only if the problem was with article or group.
|
||||
// if the problem occurs by connecting or authorization we do not
|
||||
// free the connection, to prevent starting of thousands of threads
|
||||
// (cause each of them will also free it's connection after the
|
||||
// same connect-error).
|
||||
FreeConnection(Status == adFinished || Status == adNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pConnection)
|
||||
if (m_connection)
|
||||
{
|
||||
AddServerData();
|
||||
}
|
||||
|
||||
if (Status == adFinished || Status == adFatalError)
|
||||
if (!connected && m_connection)
|
||||
{
|
||||
detail("Article %s @ %s failed: could not establish connection", *m_infoName, *m_connectionName);
|
||||
}
|
||||
|
||||
if (status == adConnectError)
|
||||
{
|
||||
connected = false;
|
||||
status = adFailed;
|
||||
}
|
||||
|
||||
if (connected && status == adFailed)
|
||||
{
|
||||
remainedRetries--;
|
||||
}
|
||||
|
||||
bool optionalBlocked = false;
|
||||
if (!connected && m_connection && !IsStopped())
|
||||
{
|
||||
g_ServerPool->BlockServer(lastServer);
|
||||
optionalBlocked = lastServer->GetOptional();
|
||||
}
|
||||
|
||||
wantServer = nullptr;
|
||||
if (connected && status == adFailed && remainedRetries > 0 && !retentionFailure)
|
||||
{
|
||||
wantServer = lastServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeConnection(status == adFinished || status == adNotFound);
|
||||
}
|
||||
|
||||
if (status == adFinished || status == adFatalError)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pWantServer = NULL;
|
||||
|
||||
if (bConnected && Status == adFailed)
|
||||
if (IsStopped() || ((g_WorkState->GetPauseDownload() || g_WorkState->GetQuotaReached()) && !force) ||
|
||||
(g_WorkState->GetTempPauseDownload() && !m_fileInfo->GetExtraPriority()) ||
|
||||
serverConfigGeneration != g_ServerPool->GetGeneration())
|
||||
{
|
||||
iRemainedRetries--;
|
||||
status = adRetry;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bConnected || (Status == adFailed && iRemainedRetries > 0))
|
||||
if (!wantServer && (connected || retentionFailure || optionalBlocked))
|
||||
{
|
||||
pWantServer = pLastServer;
|
||||
}
|
||||
|
||||
if (pWantServer &&
|
||||
!(IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
|
||||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
|
||||
iServerConfigGeneration != g_pServerPool->GetGeneration()))
|
||||
{
|
||||
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
|
||||
SetStatus(adWaiting);
|
||||
int msec = 0;
|
||||
while (!(IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
|
||||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
|
||||
iServerConfigGeneration != g_pServerPool->GetGeneration()) &&
|
||||
msec < g_pOptions->GetRetryInterval() * 1000)
|
||||
if (!optionalBlocked)
|
||||
{
|
||||
usleep(100 * 1000);
|
||||
msec += 100;
|
||||
failedServers.push_back(lastServer);
|
||||
}
|
||||
SetLastUpdateTimeNow();
|
||||
SetStatus(adRunning);
|
||||
}
|
||||
|
||||
if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
|
||||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
|
||||
iServerConfigGeneration != g_pServerPool->GetGeneration())
|
||||
{
|
||||
Status = adRetry;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pWantServer)
|
||||
{
|
||||
failedServers.push_back(pLastServer);
|
||||
|
||||
// if all servers from current level were tried, increase level
|
||||
// if all servers from all levels were tried, break the loop with failure status
|
||||
|
||||
bool bAllServersOnLevelFailed = true;
|
||||
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
|
||||
bool allServersOnLevelFailed = true;
|
||||
for (NewsServer* candidateServer : g_ServerPool->GetServers())
|
||||
{
|
||||
NewsServer* pCandidateServer = *it;
|
||||
if (pCandidateServer->GetNormLevel() == iLevel)
|
||||
if (candidateServer->GetNormLevel() == level)
|
||||
{
|
||||
bool bServerFailed = !pCandidateServer->GetActive() || pCandidateServer->GetMaxConnections() == 0;
|
||||
if (!bServerFailed)
|
||||
bool serverFailed = !candidateServer->GetActive() || candidateServer->GetMaxConnections() == 0 ||
|
||||
(candidateServer->GetOptional() && g_ServerPool->IsServerBlocked(candidateServer));
|
||||
if (!serverFailed)
|
||||
{
|
||||
for (Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++)
|
||||
for (NewsServer* ignoreServer : failedServers)
|
||||
{
|
||||
NewsServer* pIgnoreServer = *it;
|
||||
if (pIgnoreServer == pCandidateServer ||
|
||||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
|
||||
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
|
||||
if (ignoreServer == candidateServer ||
|
||||
(ignoreServer->GetGroup() > 0 && ignoreServer->GetGroup() == candidateServer->GetGroup() &&
|
||||
ignoreServer->GetNormLevel() == candidateServer->GetNormLevel()))
|
||||
{
|
||||
bServerFailed = true;
|
||||
serverFailed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bServerFailed)
|
||||
if (!serverFailed)
|
||||
{
|
||||
bAllServersOnLevelFailed = false;
|
||||
allServersOnLevelFailed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bAllServersOnLevelFailed)
|
||||
if (allServersOnLevelFailed)
|
||||
{
|
||||
if (iLevel < g_pServerPool->GetMaxNormLevel())
|
||||
if (level < g_ServerPool->GetMaxNormLevel())
|
||||
{
|
||||
detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel);
|
||||
iLevel++;
|
||||
detail("Article %s @ all level %i servers failed, increasing level", *m_infoName, level);
|
||||
level++;
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Article %s @ all servers failed", m_szInfoName);
|
||||
Status = adFailed;
|
||||
detail("Article %s @ all servers failed", *m_infoName);
|
||||
status = adFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iRemainedRetries = iRetries;
|
||||
|
||||
remainedRetries = retries;
|
||||
}
|
||||
}
|
||||
|
||||
FreeConnection(Status == adFinished);
|
||||
FreeConnection(status == adFinished);
|
||||
|
||||
if (m_ArticleWriter.GetDuplicate())
|
||||
if (m_articleWriter.GetDuplicate())
|
||||
{
|
||||
Status = adFinished;
|
||||
status = adFinished;
|
||||
}
|
||||
|
||||
if (Status != adFinished && Status != adRetry)
|
||||
if (status != adFinished && status != adRetry)
|
||||
{
|
||||
Status = adFailed;
|
||||
status = adFailed;
|
||||
}
|
||||
|
||||
if (IsStopped())
|
||||
{
|
||||
detail("Download %s cancelled", m_szInfoName);
|
||||
Status = adRetry;
|
||||
detail("Download %s cancelled", *m_infoName);
|
||||
status = adRetry;
|
||||
}
|
||||
|
||||
if (Status == adFailed)
|
||||
if (status == adFailed)
|
||||
{
|
||||
detail("Download %s failed", m_szInfoName);
|
||||
detail("Download %s failed", *m_infoName);
|
||||
}
|
||||
|
||||
SetStatus(Status);
|
||||
Notify(NULL);
|
||||
SetStatus(status);
|
||||
Notify(nullptr);
|
||||
|
||||
debug("Exiting ArticleDownloader-loop");
|
||||
}
|
||||
|
||||
ArticleDownloader::EStatus ArticleDownloader::Download()
|
||||
{
|
||||
const char* szResponse = NULL;
|
||||
EStatus Status = adRunning;
|
||||
m_bWritingStarted = false;
|
||||
m_pArticleInfo->SetCrc(0);
|
||||
const char* response = nullptr;
|
||||
EStatus status = adRunning;
|
||||
m_writingStarted = false;
|
||||
m_articleInfo->SetCrc(0);
|
||||
|
||||
if (m_pConnection->GetNewsServer()->GetJoinGroup())
|
||||
if (m_contentAnalyzer)
|
||||
{
|
||||
m_contentAnalyzer->Reset();
|
||||
}
|
||||
|
||||
if (m_connection->GetNewsServer()->GetJoinGroup())
|
||||
{
|
||||
// change group
|
||||
for (FileInfo::Groups::iterator it = m_pFileInfo->GetGroups()->begin(); it != m_pFileInfo->GetGroups()->end(); it++)
|
||||
for (CString& group : m_fileInfo->GetGroups())
|
||||
{
|
||||
szResponse = m_pConnection->JoinGroup(*it);
|
||||
if (szResponse && !strncmp(szResponse, "2", 1))
|
||||
response = m_connection->JoinGroup(group);
|
||||
if (response && !strncmp(response, "2", 1))
|
||||
{
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Status = CheckResponse(szResponse, "could not join group");
|
||||
if (Status != adFinished)
|
||||
status = CheckResponse(response, "could not join group");
|
||||
if (status != adFinished)
|
||||
{
|
||||
return Status;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve article
|
||||
char tmp[1024];
|
||||
snprintf(tmp, 1024, "ARTICLE %s\r\n", m_pArticleInfo->GetMessageID());
|
||||
tmp[1024-1] = '\0';
|
||||
response = m_connection->Request(BString<1024>("%s %s\r\n",
|
||||
g_Options->GetRawArticle() ? "ARTICLE" : "BODY", m_articleInfo->GetMessageId()));
|
||||
|
||||
for (int retry = 3; retry > 0; retry--)
|
||||
status = CheckResponse(response, "could not fetch article");
|
||||
if (status != adFinished)
|
||||
{
|
||||
szResponse = m_pConnection->Request(tmp);
|
||||
if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError())
|
||||
{
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status = CheckResponse(szResponse, "could not fetch article");
|
||||
if (Status != adFinished)
|
||||
m_decoder.Clear();
|
||||
m_decoder.SetCrcCheck(g_Options->GetCrcCheck());
|
||||
m_decoder.SetRawMode(g_Options->GetRawArticle());
|
||||
|
||||
status = adRunning;
|
||||
CharBuffer lineBuf(1024*4);
|
||||
|
||||
while (!IsStopped() && !m_decoder.GetEof())
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
if (g_pOptions->GetDecode())
|
||||
{
|
||||
m_YDecoder.Clear();
|
||||
m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck());
|
||||
m_UDecoder.Clear();
|
||||
}
|
||||
|
||||
bool bBody = false;
|
||||
bool bEnd = false;
|
||||
const int LineBufSize = 1024*10;
|
||||
char* szLineBuf = (char*)malloc(LineBufSize);
|
||||
Status = adRunning;
|
||||
|
||||
while (!IsStopped())
|
||||
{
|
||||
time_t tOldTime = m_tLastUpdateTime;
|
||||
SetLastUpdateTimeNow();
|
||||
if (tOldTime != m_tLastUpdateTime)
|
||||
{
|
||||
AddServerData();
|
||||
}
|
||||
|
||||
// Throttle the bandwidth
|
||||
while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) &&
|
||||
(g_pStatMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate()))
|
||||
// throttle the bandwidth
|
||||
while (!IsStopped() && (g_WorkState->GetSpeedLimit() > 0.0f) &&
|
||||
(g_StatMeter->CalcCurrentDownloadSpeed() > g_WorkState->GetSpeedLimit() ||
|
||||
g_StatMeter->CalcMomentaryDownloadSpeed() > g_WorkState->GetSpeedLimit()))
|
||||
{
|
||||
SetLastUpdateTimeNow();
|
||||
usleep(10 * 1000);
|
||||
Util::Sleep(10);
|
||||
}
|
||||
|
||||
int iLen = 0;
|
||||
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
|
||||
|
||||
g_pStatMeter->AddSpeedReading(iLen);
|
||||
if (g_pOptions->GetAccurateRate())
|
||||
char* buffer;
|
||||
int len;
|
||||
m_connection->ReadBuffer(&buffer, &len);
|
||||
if (len == 0)
|
||||
{
|
||||
AddServerData();
|
||||
len = m_connection->TryRecv(lineBuf, lineBuf.Size());
|
||||
buffer = lineBuf;
|
||||
}
|
||||
|
||||
// Have we encountered a timeout?
|
||||
if (!line)
|
||||
// have we encountered a timeout?
|
||||
if (len <= 0)
|
||||
{
|
||||
if (!IsStopped())
|
||||
{
|
||||
detail("Article %s @ %s failed: Unexpected end of article", m_szInfoName, m_szConnectionName);
|
||||
detail("Article %s @ %s failed: Unexpected end of article", *m_infoName, *m_connectionName);
|
||||
}
|
||||
Status = adFailed;
|
||||
status = adFailed;
|
||||
break;
|
||||
}
|
||||
|
||||
//detect end of article
|
||||
if (!strcmp(line, ".\r\n") || !strcmp(line, ".\n"))
|
||||
g_StatMeter->AddSpeedReading(len);
|
||||
time_t oldTime = m_lastUpdateTime;
|
||||
SetLastUpdateTimeNow();
|
||||
if (oldTime != m_lastUpdateTime)
|
||||
{
|
||||
bEnd = true;
|
||||
break;
|
||||
AddServerData();
|
||||
}
|
||||
|
||||
//detect lines starting with "." (marked as "..")
|
||||
if (!strncmp(line, "..", 2))
|
||||
{
|
||||
line++;
|
||||
iLen--;
|
||||
}
|
||||
|
||||
if (!bBody)
|
||||
{
|
||||
// detect body of article
|
||||
if (*line == '\r' || *line == '\n')
|
||||
{
|
||||
bBody = true;
|
||||
}
|
||||
// check id of returned article
|
||||
else if (!strncmp(line, "Message-ID: ", 12))
|
||||
{
|
||||
char* p = line + 12;
|
||||
if (strncmp(p, m_pArticleInfo->GetMessageID(), strlen(m_pArticleInfo->GetMessageID())))
|
||||
{
|
||||
if (char* e = strrchr(p, '\r')) *e = '\0'; // remove trailing CR-character
|
||||
detail("Article %s @ %s failed: Wrong message-id, expected %s, returned %s", m_szInfoName,
|
||||
m_szConnectionName, m_pArticleInfo->GetMessageID(), p);
|
||||
Status = adFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUnknown && g_pOptions->GetDecode())
|
||||
{
|
||||
m_eFormat = Decoder::DetectFormat(line, iLen);
|
||||
}
|
||||
// decode article data
|
||||
len = m_decoder.DecodeBuffer(buffer, len);
|
||||
|
||||
// write to output file
|
||||
if (((bBody && m_eFormat != Decoder::efUnknown) || !g_pOptions->GetDecode()) && !Write(line, iLen))
|
||||
if (len > 0 && !Write(buffer, len))
|
||||
{
|
||||
Status = adFatalError;
|
||||
status = adFatalError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(szLineBuf);
|
||||
|
||||
if (!bEnd && Status == adRunning && !IsStopped())
|
||||
{
|
||||
detail("Article %s @ %s failed: article incomplete", m_szInfoName, m_szConnectionName);
|
||||
Status = adFailed;
|
||||
}
|
||||
|
||||
if (IsStopped())
|
||||
{
|
||||
Status = adFailed;
|
||||
status = adFailed;
|
||||
}
|
||||
|
||||
if (Status == adRunning)
|
||||
if (status == adRunning)
|
||||
{
|
||||
FreeConnection(true);
|
||||
Status = DecodeCheck();
|
||||
status = DecodeCheck();
|
||||
}
|
||||
|
||||
if (m_bWritingStarted)
|
||||
if (m_writingStarted)
|
||||
{
|
||||
m_ArticleWriter.Finish(Status == adFinished);
|
||||
m_articleWriter.Finish(status == adFinished);
|
||||
}
|
||||
|
||||
if (Status == adFinished)
|
||||
if (status == adFinished)
|
||||
{
|
||||
detail("Successfully downloaded %s", m_szInfoName);
|
||||
detail("Successfully downloaded %s", *m_infoName);
|
||||
}
|
||||
|
||||
return Status;
|
||||
return status;
|
||||
}
|
||||
|
||||
ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szResponse, const char* szComment)
|
||||
ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* response, const char* comment)
|
||||
{
|
||||
if (!szResponse)
|
||||
if (!response)
|
||||
{
|
||||
if (!IsStopped())
|
||||
{
|
||||
detail("Article %s @ %s failed, %s: Connection closed by remote host",
|
||||
m_szInfoName, m_szConnectionName, szComment);
|
||||
*m_infoName, *m_connectionName, comment);
|
||||
}
|
||||
return adConnectError;
|
||||
}
|
||||
else if (m_pConnection->GetAuthError() || !strncmp(szResponse, "400", 3) || !strncmp(szResponse, "499", 3))
|
||||
else if (m_connection->GetAuthError() || !strncmp(response, "400", 3) || !strncmp(response, "499", 3))
|
||||
{
|
||||
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
|
||||
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
|
||||
return adConnectError;
|
||||
}
|
||||
else if (!strncmp(szResponse, "41", 2) || !strncmp(szResponse, "42", 2) || !strncmp(szResponse, "43", 2))
|
||||
else if (!strncmp(response, "41", 2) || !strncmp(response, "42", 2) || !strncmp(response, "43", 2))
|
||||
{
|
||||
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
|
||||
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
|
||||
return adNotFound;
|
||||
}
|
||||
else if (!strncmp(szResponse, "2", 1))
|
||||
else if (!strncmp(response, "2", 1))
|
||||
{
|
||||
// OK
|
||||
return adFinished;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
// unknown error, no special handling
|
||||
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
|
||||
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
|
||||
return adFailed;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArticleDownloader::Write(char* szLine, int iLen)
|
||||
bool ArticleDownloader::Write(char* buffer, int len)
|
||||
{
|
||||
const char* szArticleFilename = NULL;
|
||||
long long iArticleFileSize = 0;
|
||||
long long iArticleOffset = 0;
|
||||
int iArticleSize = 0;
|
||||
const char* articleFilename = nullptr;
|
||||
int64 articleFileSize = 0;
|
||||
int64 articleOffset = 0;
|
||||
int articleSize = 0;
|
||||
|
||||
if (g_pOptions->GetDecode())
|
||||
if (!m_writingStarted)
|
||||
{
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
if (!g_Options->GetRawArticle())
|
||||
{
|
||||
iLen = m_YDecoder.DecodeBuffer(szLine, iLen);
|
||||
szArticleFilename = m_YDecoder.GetArticleFilename();
|
||||
iArticleFileSize = m_YDecoder.GetSize();
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUx)
|
||||
{
|
||||
iLen = m_UDecoder.DecodeBuffer(szLine, iLen);
|
||||
szArticleFilename = m_UDecoder.GetArticleFilename();
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Decoding %s failed: unsupported encoding", m_szInfoName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iLen > 0 && m_eFormat == Decoder::efYenc)
|
||||
{
|
||||
if (m_YDecoder.GetBegin() == 0 || m_YDecoder.GetEnd() == 0)
|
||||
articleFilename = m_decoder.GetArticleFilename();
|
||||
if (m_decoder.GetFormat() == Decoder::efYenc)
|
||||
{
|
||||
return false;
|
||||
if (m_decoder.GetBeginPos() == 0 || m_decoder.GetEndPos() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
articleFileSize = m_decoder.GetSize();
|
||||
articleOffset = m_decoder.GetBeginPos() - 1;
|
||||
articleSize = (int)(m_decoder.GetEndPos() - m_decoder.GetBeginPos() + 1);
|
||||
if (articleSize <= 0 || articleSize > 1024*1024*1024)
|
||||
{
|
||||
warn("Malformed article %s: size %i out of range", *m_infoName, articleSize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
iArticleOffset = m_YDecoder.GetBegin() - 1;
|
||||
iArticleSize = (int)(m_YDecoder.GetEnd() - m_YDecoder.GetBegin() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_bWritingStarted && iLen > 0)
|
||||
{
|
||||
if (!m_ArticleWriter.Start(m_eFormat, szArticleFilename, iArticleFileSize, iArticleOffset, iArticleSize))
|
||||
if (!m_articleWriter.Start(m_decoder.GetFormat(), articleFilename, articleFileSize, articleOffset, articleSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_bWritingStarted = true;
|
||||
m_writingStarted = true;
|
||||
}
|
||||
|
||||
bool bOK = iLen == 0 || m_ArticleWriter.Write(szLine, iLen);
|
||||
bool ok = m_articleWriter.Write(buffer, len);
|
||||
|
||||
return bOK;
|
||||
if (m_contentAnalyzer)
|
||||
{
|
||||
m_contentAnalyzer->Append(buffer, len);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
|
||||
{
|
||||
if (g_pOptions->GetDecode())
|
||||
if (!g_Options->GetRawArticle())
|
||||
{
|
||||
Decoder* pDecoder = NULL;
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
{
|
||||
pDecoder = &m_YDecoder;
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUx)
|
||||
{
|
||||
pDecoder = &m_UDecoder;
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName);
|
||||
return adFailed;
|
||||
}
|
||||
Decoder::EStatus status = m_decoder.Check();
|
||||
|
||||
Decoder::EStatus eStatus = pDecoder->Check();
|
||||
|
||||
if (eStatus == Decoder::eFinished)
|
||||
if (status == Decoder::dsFinished)
|
||||
{
|
||||
if (pDecoder->GetArticleFilename())
|
||||
if (m_decoder.GetArticleFilename())
|
||||
{
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = strdup(pDecoder->GetArticleFilename());
|
||||
m_articleFilename = m_decoder.GetArticleFilename();
|
||||
}
|
||||
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
if (m_decoder.GetFormat() == Decoder::efYenc)
|
||||
{
|
||||
m_pArticleInfo->SetCrc(g_pOptions->GetCrcCheck() ?
|
||||
m_YDecoder.GetCalculatedCrc() : m_YDecoder.GetExpectedCrc());
|
||||
m_articleInfo->SetCrc(g_Options->GetCrcCheck() ?
|
||||
m_decoder.GetCalculatedCrc() : m_decoder.GetExpectedCrc());
|
||||
}
|
||||
|
||||
return adFinished;
|
||||
}
|
||||
else if (eStatus == Decoder::eCrcError)
|
||||
else if (status == Decoder::dsCrcError)
|
||||
{
|
||||
detail("Decoding %s failed: CRC-Error", m_szInfoName);
|
||||
detail("Decoding %s failed: CRC-Error", *m_infoName);
|
||||
return adCrcError;
|
||||
}
|
||||
else if (eStatus == Decoder::eArticleIncomplete)
|
||||
else if (status == Decoder::dsArticleIncomplete)
|
||||
{
|
||||
detail("Decoding %s failed: article incomplete", m_szInfoName);
|
||||
detail("Decoding %s failed: article incomplete", *m_infoName);
|
||||
return adFailed;
|
||||
}
|
||||
else if (eStatus == Decoder::eInvalidSize)
|
||||
else if (status == Decoder::dsInvalidSize)
|
||||
{
|
||||
detail("Decoding %s failed: size mismatch", m_szInfoName);
|
||||
detail("Decoding %s failed: size mismatch", *m_infoName);
|
||||
return adFailed;
|
||||
}
|
||||
else if (eStatus == Decoder::eNoBinaryData)
|
||||
else if (status == Decoder::dsNoBinaryData)
|
||||
{
|
||||
detail("Decoding %s failed: no binary data found", m_szInfoName);
|
||||
detail("Decoding %s failed: no binary data found", *m_infoName);
|
||||
return adFailed;
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Decoding %s failed", m_szInfoName);
|
||||
detail("Decoding %s failed", *m_infoName);
|
||||
return adFailed;
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
return adFinished;
|
||||
}
|
||||
}
|
||||
|
||||
void ArticleDownloader::SetLastUpdateTimeNow()
|
||||
{
|
||||
m_lastUpdateTime = Util::CurrentTime();
|
||||
}
|
||||
|
||||
void ArticleDownloader::LogDebugInfo()
|
||||
{
|
||||
char szTime[50];
|
||||
#ifdef HAVE_CTIME_R_3
|
||||
ctime_r(&m_tLastUpdateTime, szTime, 50);
|
||||
#else
|
||||
ctime_r(&m_tLastUpdateTime, szTime);
|
||||
#endif
|
||||
info(" Download: Status=%i, LastUpdateTime=%s, InfoName=%s", m_eStatus, szTime, m_szInfoName);
|
||||
info(" Download: status=%i, LastUpdateTime=%s, InfoName=%s", m_status,
|
||||
*Util::FormatTime(m_lastUpdateTime), *m_infoName);
|
||||
}
|
||||
|
||||
void ArticleDownloader::Stop()
|
||||
{
|
||||
debug("Trying to stop ArticleDownloader");
|
||||
Thread::Stop();
|
||||
m_mutexConnection.Lock();
|
||||
if (m_pConnection)
|
||||
Guard guard(m_connectionMutex);
|
||||
if (m_connection)
|
||||
{
|
||||
m_pConnection->SetSuppressErrors(true);
|
||||
m_pConnection->Cancel();
|
||||
m_connection->SetSuppressErrors(true);
|
||||
m_connection->Cancel();
|
||||
}
|
||||
m_mutexConnection.Unlock();
|
||||
debug("ArticleDownloader stopped successfully");
|
||||
}
|
||||
|
||||
bool ArticleDownloader::Terminate()
|
||||
void ArticleDownloader::FreeConnection(bool keepConnected)
|
||||
{
|
||||
NNTPConnection* pConnection = m_pConnection;
|
||||
bool terminated = Kill();
|
||||
if (terminated && pConnection)
|
||||
{
|
||||
debug("Terminating connection");
|
||||
pConnection->SetSuppressErrors(true);
|
||||
pConnection->Cancel();
|
||||
pConnection->Disconnect();
|
||||
g_pStatMeter->AddServerData(pConnection->FetchTotalBytesRead(), pConnection->GetNewsServer()->GetID());
|
||||
g_pServerPool->FreeConnection(pConnection, true);
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
|
||||
void ArticleDownloader::FreeConnection(bool bKeepConnected)
|
||||
{
|
||||
if (m_pConnection)
|
||||
if (m_connection)
|
||||
{
|
||||
debug("Releasing connection");
|
||||
m_mutexConnection.Lock();
|
||||
if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled)
|
||||
Guard guard(m_connectionMutex);
|
||||
if (!keepConnected || m_connection->GetStatus() == Connection::csCancelled)
|
||||
{
|
||||
m_pConnection->Disconnect();
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
AddServerData();
|
||||
g_pServerPool->FreeConnection(m_pConnection, true);
|
||||
m_pConnection = NULL;
|
||||
m_mutexConnection.Unlock();
|
||||
g_ServerPool->FreeConnection(m_connection, true);
|
||||
m_connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ArticleDownloader::AddServerData()
|
||||
{
|
||||
int iBytesRead = m_pConnection->FetchTotalBytesRead();
|
||||
g_pStatMeter->AddServerData(iBytesRead, m_pConnection->GetNewsServer()->GetID());
|
||||
m_iDownloadedSize += iBytesRead;
|
||||
int bytesRead = m_connection->FetchTotalBytesRead();
|
||||
g_StatMeter->AddServerData(bytesRead, m_connection->GetNewsServer()->GetId());
|
||||
m_downloadedSize += bytesRead;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,26 +15,29 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ARTICLEDOWNLOADER_H
|
||||
#define ARTICLEDOWNLOADER_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Observer.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "Thread.h"
|
||||
#include "NNTPConnection.h"
|
||||
#include "NntpConnection.h"
|
||||
#include "Decoder.h"
|
||||
#include "ArticleWriter.h"
|
||||
#include "Util.h"
|
||||
|
||||
class ArticleContentAnalyzer
|
||||
{
|
||||
public:
|
||||
virtual ~ArticleContentAnalyzer() {};
|
||||
virtual void Reset() = 0;
|
||||
virtual void Append(const void* buffer, int len) = 0;
|
||||
};
|
||||
|
||||
class ArticleDownloader : public Thread, public Subject
|
||||
{
|
||||
@@ -53,65 +56,54 @@ public:
|
||||
adFatalError
|
||||
};
|
||||
|
||||
class ArticleWriterImpl : public ArticleWriter
|
||||
{
|
||||
private:
|
||||
ArticleDownloader* m_pOwner;
|
||||
protected:
|
||||
virtual void SetLastUpdateTimeNow() { m_pOwner->SetLastUpdateTimeNow(); }
|
||||
public:
|
||||
void SetOwner(ArticleDownloader* pOwner) { m_pOwner = pOwner; }
|
||||
};
|
||||
|
||||
ArticleDownloader();
|
||||
virtual ~ArticleDownloader();
|
||||
void SetFileInfo(FileInfo* fileInfo) { m_fileInfo = fileInfo; }
|
||||
FileInfo* GetFileInfo() { return m_fileInfo; }
|
||||
void SetArticleInfo(ArticleInfo* articleInfo) { m_articleInfo = articleInfo; }
|
||||
ArticleInfo* GetArticleInfo() { return m_articleInfo; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
ServerStatList* GetServerStats() { return &m_serverStats; }
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
time_t GetLastUpdateTime() { return m_lastUpdateTime; }
|
||||
void SetLastUpdateTimeNow();
|
||||
const char* GetArticleFilename() { return m_articleFilename; }
|
||||
void SetInfoName(const char* infoName);
|
||||
const char* GetInfoName() { return m_infoName; }
|
||||
const char* GetConnectionName() { return m_connectionName; }
|
||||
void SetConnection(NntpConnection* connection) { m_connection = connection; }
|
||||
void CompleteFileParts() { m_articleWriter.CompleteFileParts(); }
|
||||
int GetDownloadedSize() { return m_downloadedSize; }
|
||||
void SetContentAnalyzer(std::unique_ptr<ArticleContentAnalyzer> contentAnalyzer) { m_contentAnalyzer = std::move(contentAnalyzer); }
|
||||
ArticleContentAnalyzer* GetContentAnalyzer() { return m_contentAnalyzer.get(); }
|
||||
|
||||
void LogDebugInfo();
|
||||
|
||||
private:
|
||||
FileInfo* m_pFileInfo;
|
||||
ArticleInfo* m_pArticleInfo;
|
||||
NNTPConnection* m_pConnection;
|
||||
EStatus m_eStatus;
|
||||
Mutex m_mutexConnection;
|
||||
char* m_szInfoName;
|
||||
char m_szConnectionName[250];
|
||||
char* m_szArticleFilename;
|
||||
time_t m_tLastUpdateTime;
|
||||
Decoder::EFormat m_eFormat;
|
||||
YDecoder m_YDecoder;
|
||||
UDecoder m_UDecoder;
|
||||
ArticleWriterImpl m_ArticleWriter;
|
||||
ServerStatList m_ServerStats;
|
||||
bool m_bWritingStarted;
|
||||
int m_iDownloadedSize;
|
||||
FileInfo* m_fileInfo;
|
||||
ArticleInfo* m_articleInfo;
|
||||
NntpConnection* m_connection = nullptr;
|
||||
EStatus m_status = adUndefined;
|
||||
Mutex m_connectionMutex;
|
||||
CString m_infoName;
|
||||
CString m_connectionName;
|
||||
CString m_articleFilename;
|
||||
time_t m_lastUpdateTime;
|
||||
Decoder m_decoder;
|
||||
ArticleWriter m_articleWriter;
|
||||
ServerStatList m_serverStats;
|
||||
bool m_writingStarted;
|
||||
int m_downloadedSize = 0;
|
||||
std::unique_ptr<ArticleContentAnalyzer> m_contentAnalyzer;
|
||||
|
||||
EStatus Download();
|
||||
EStatus DecodeCheck();
|
||||
void FreeConnection(bool bKeepConnected);
|
||||
EStatus CheckResponse(const char* szResponse, const char* szComment);
|
||||
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
|
||||
bool Write(char* szLine, int iLen);
|
||||
void AddServerData();
|
||||
|
||||
public:
|
||||
ArticleDownloader();
|
||||
virtual ~ArticleDownloader();
|
||||
void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; }
|
||||
FileInfo* GetFileInfo() { return m_pFileInfo; }
|
||||
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
|
||||
ArticleInfo* GetArticleInfo() { return m_pArticleInfo; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
ServerStatList* GetServerStats() { return &m_ServerStats; }
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
bool Terminate();
|
||||
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
|
||||
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
|
||||
const char* GetArticleFilename() { return m_szArticleFilename; }
|
||||
void SetInfoName(const char* szInfoName);
|
||||
const char* GetInfoName() { return m_szInfoName; }
|
||||
const char* GetConnectionName() { return m_szConnectionName; }
|
||||
void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; }
|
||||
void CompleteFileParts() { m_ArticleWriter.CompleteFileParts(); }
|
||||
int GetDownloadedSize() { return m_iDownloadedSize; }
|
||||
|
||||
void LogDebugInfo();
|
||||
EStatus Download();
|
||||
EStatus DecodeCheck();
|
||||
void FreeConnection(bool keepConnected);
|
||||
EStatus CheckResponse(const char* response, const char* comment);
|
||||
void SetStatus(EStatus status) { m_status = status; }
|
||||
bool Write(char* buffer, int len);
|
||||
void AddServerData();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2014-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,89 +14,109 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ARTICLEWRITER_H
|
||||
#define ARTICLEWRITER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "Decoder.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
class CachedSegmentData : public SegmentData
|
||||
{
|
||||
public:
|
||||
CachedSegmentData() {}
|
||||
CachedSegmentData(char* data, int size) : m_data(data), m_size(size) {}
|
||||
CachedSegmentData(const CachedSegmentData&) = delete;
|
||||
CachedSegmentData(CachedSegmentData&& other) :
|
||||
m_data(other.m_data), m_size(other.m_size) { other.m_data = nullptr; other.m_size = 0; }
|
||||
CachedSegmentData& operator=(CachedSegmentData&& other);
|
||||
virtual ~CachedSegmentData();
|
||||
virtual char* GetData() { return m_data; }
|
||||
|
||||
private:
|
||||
char* m_data = nullptr;
|
||||
int m_size = 0;
|
||||
|
||||
friend class ArticleCache;
|
||||
};
|
||||
|
||||
class ArticleWriter
|
||||
{
|
||||
private:
|
||||
FileInfo* m_pFileInfo;
|
||||
ArticleInfo* m_pArticleInfo;
|
||||
FILE* m_pOutFile;
|
||||
char* m_szTempFilename;
|
||||
char* m_szOutputFilename;
|
||||
const char* m_szResultFilename;
|
||||
Decoder::EFormat m_eFormat;
|
||||
char* m_pArticleData;
|
||||
long long m_iArticleOffset;
|
||||
int m_iArticleSize;
|
||||
int m_iArticlePtr;
|
||||
bool m_bFlushing;
|
||||
bool m_bDuplicate;
|
||||
char* m_szInfoName;
|
||||
|
||||
bool PrepareFile(char* szLine);
|
||||
bool CreateOutputFile(long long iSize);
|
||||
void BuildOutputFilename();
|
||||
bool IsFileCached();
|
||||
void SetWriteBuffer(FILE* pOutFile, int iRecSize);
|
||||
|
||||
protected:
|
||||
virtual void SetLastUpdateTimeNow() {}
|
||||
|
||||
public:
|
||||
ArticleWriter();
|
||||
~ArticleWriter();
|
||||
void SetInfoName(const char* szInfoName);
|
||||
void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; }
|
||||
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
|
||||
void Prepare();
|
||||
bool Start(Decoder::EFormat eFormat, const char* szFilename, long long iFileSize, long long iArticleOffset, int iArticleSize);
|
||||
bool Write(char* szBufffer, int iLen);
|
||||
void Finish(bool bSuccess);
|
||||
bool GetDuplicate() { return m_bDuplicate; }
|
||||
void CompleteFileParts();
|
||||
static bool MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir);
|
||||
void FlushCache();
|
||||
void SetInfoName(const char* infoName) { m_infoName = infoName; }
|
||||
void SetFileInfo(FileInfo* fileInfo) { m_fileInfo = fileInfo; }
|
||||
void SetArticleInfo(ArticleInfo* articleInfo) { m_articleInfo = articleInfo; }
|
||||
void Prepare();
|
||||
bool Start(Decoder::EFormat format, const char* filename, int64 fileSize, int64 articleOffset, int articleSize);
|
||||
bool Write(char* buffer, int len);
|
||||
void Finish(bool success);
|
||||
bool GetDuplicate() { return m_duplicate; }
|
||||
void CompleteFileParts();
|
||||
static bool MoveCompletedFiles(NzbInfo* nzbInfo, const char* oldDestDir);
|
||||
void FlushCache();
|
||||
|
||||
private:
|
||||
FileInfo* m_fileInfo;
|
||||
ArticleInfo* m_articleInfo;
|
||||
DiskFile m_outFile;
|
||||
CString m_tempFilename;
|
||||
CString m_outputFilename;
|
||||
const char* m_resultFilename = nullptr;
|
||||
Decoder::EFormat m_format = Decoder::efUnknown;
|
||||
CachedSegmentData m_articleData;
|
||||
int64 m_articleOffset;
|
||||
int m_articleSize;
|
||||
int m_articlePtr;
|
||||
bool m_duplicate = false;
|
||||
CString m_infoName;
|
||||
|
||||
bool CreateOutputFile(int64 size);
|
||||
void BuildOutputFilename();
|
||||
void SetWriteBuffer(DiskFile& outFile, int recSize);
|
||||
};
|
||||
|
||||
class ArticleCache : public Thread
|
||||
{
|
||||
private:
|
||||
size_t m_iAllocated;
|
||||
bool m_bFlushing;
|
||||
Mutex m_mutexAlloc;
|
||||
Mutex m_mutexFlush;
|
||||
Mutex m_mutexContent;
|
||||
FileInfo* m_pFileInfo;
|
||||
|
||||
bool CheckFlush(bool bFlushEverything);
|
||||
|
||||
public:
|
||||
ArticleCache();
|
||||
virtual void Run();
|
||||
void* Alloc(int iSize);
|
||||
void* Realloc(void* buf, int iOldSize, int iNewSize);
|
||||
void Free(int iSize);
|
||||
void LockFlush();
|
||||
void UnlockFlush();
|
||||
void LockContent() { m_mutexContent.Lock(); }
|
||||
void UnlockContent() { m_mutexContent.Unlock(); }
|
||||
bool GetFlushing() { return m_bFlushing; }
|
||||
size_t GetAllocated() { return m_iAllocated; }
|
||||
bool FileBusy(FileInfo* pFileInfo) { return pFileInfo == m_pFileInfo; }
|
||||
class FlushGuard
|
||||
{
|
||||
public:
|
||||
FlushGuard(FlushGuard&& other) = default;
|
||||
~FlushGuard();
|
||||
private:
|
||||
Guard m_guard;
|
||||
FlushGuard(Mutex& mutex);
|
||||
friend class ArticleCache;
|
||||
};
|
||||
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
CachedSegmentData Alloc(int size);
|
||||
bool Realloc(CachedSegmentData* segment, int newSize);
|
||||
void Free(CachedSegmentData* segment);
|
||||
FlushGuard GuardFlush() { return FlushGuard(m_flushMutex); }
|
||||
Guard GuardContent() { return Guard(m_contentMutex); }
|
||||
bool GetFlushing() { return m_flushing; }
|
||||
size_t GetAllocated() { return m_allocated; }
|
||||
bool FileBusy(FileInfo* fileInfo) { return fileInfo == m_fileInfo; }
|
||||
|
||||
private:
|
||||
size_t m_allocated = 0;
|
||||
bool m_flushing = false;
|
||||
Mutex m_allocMutex;
|
||||
Mutex m_flushMutex;
|
||||
Mutex m_contentMutex;
|
||||
FileInfo* m_fileInfo = nullptr;
|
||||
ConditionVar m_allocCond;
|
||||
|
||||
bool CheckFlush(bool flushEverything);
|
||||
};
|
||||
|
||||
extern ArticleCache* g_ArticleCache;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,55 +14,127 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Decoder.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
|
||||
const char* Decoder::FormatNames[] = { "Unknown", "yEnc", "UU" };
|
||||
#include "YEncode.h"
|
||||
|
||||
Decoder::Decoder()
|
||||
{
|
||||
debug("Creating Decoder");
|
||||
debug("%s", YEncode::decode_simd ? "SIMD yEnc decoder can be used" : "SIMD yEnc decoder isn't available for this CPU");
|
||||
debug("%s", YEncode::crc_simd ? "SIMD Crc routine can be used" : "SIMD Crc routine isn't available for this CPU");
|
||||
|
||||
m_szArticleFilename = NULL;
|
||||
}
|
||||
|
||||
Decoder::~ Decoder()
|
||||
{
|
||||
debug("Destroying Decoder");
|
||||
|
||||
free(m_szArticleFilename);
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Decoder::Clear()
|
||||
{
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = NULL;
|
||||
m_articleFilename.Clear();
|
||||
m_body = false;
|
||||
m_begin = false;
|
||||
m_part = false;
|
||||
m_end = false;
|
||||
m_crc = false;
|
||||
m_eof = false;
|
||||
m_expectedCRC = 0;
|
||||
m_crc32.Reset();
|
||||
m_beginPos = 0;
|
||||
m_endPos = 0;
|
||||
m_size = 0;
|
||||
m_endSize = 0;
|
||||
m_outSize = 0;
|
||||
m_state = 0;
|
||||
m_crcCheck = false;
|
||||
m_lineBuf.Reserve(1024*8);
|
||||
m_lineBuf.SetLength(0);
|
||||
}
|
||||
|
||||
/* At the beginning of article the processing goes line by line to find '=ybegin'-marker.
|
||||
* Once the yEnc-data is started switches to blockwise processing.
|
||||
* At the end of yEnc-data switches back to line by line mode to
|
||||
* process '=yend'-marker and EOF-marker.
|
||||
* UU-encoded articles are processed completely in line by line mode.
|
||||
*/
|
||||
int Decoder::DecodeBuffer(char* buffer, int len)
|
||||
{
|
||||
if (m_rawMode)
|
||||
{
|
||||
ProcessRaw(buffer, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
int outlen = 0;
|
||||
|
||||
if (m_body && m_format == efYenc)
|
||||
{
|
||||
outlen = DecodeYenc(buffer, buffer, len);
|
||||
if (m_body)
|
||||
{
|
||||
return outlen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lineBuf.Append(buffer, len);
|
||||
}
|
||||
|
||||
char* line = (char*)m_lineBuf;
|
||||
while (char* end = strchr(line, '\n'))
|
||||
{
|
||||
int llen = (int)(end - line + 1);
|
||||
|
||||
if (line[0] == '.' && line[1] == '\r')
|
||||
{
|
||||
m_eof = true;
|
||||
m_lineBuf.SetLength(0);
|
||||
return outlen;
|
||||
}
|
||||
|
||||
if (m_format == efUnknown)
|
||||
{
|
||||
m_format = DetectFormat(line, llen);
|
||||
}
|
||||
|
||||
if (m_format == efYenc)
|
||||
{
|
||||
ProcessYenc(line, llen);
|
||||
if (m_body)
|
||||
{
|
||||
outlen = DecodeYenc(end + 1, buffer, m_lineBuf.Length() - (int)(end + 1 - m_lineBuf));
|
||||
if (m_body)
|
||||
{
|
||||
m_lineBuf.SetLength(0);
|
||||
return outlen;
|
||||
}
|
||||
line = (char*)m_lineBuf;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (m_format == efUx)
|
||||
{
|
||||
outlen += DecodeUx(line, llen);
|
||||
}
|
||||
|
||||
line = end + 1;
|
||||
}
|
||||
|
||||
if (*line)
|
||||
{
|
||||
len = m_lineBuf.Length() - (int)(line - m_lineBuf);
|
||||
memmove((char*)m_lineBuf, line, len);
|
||||
m_lineBuf.SetLength(len);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lineBuf.SetLength(0);
|
||||
}
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
|
||||
@@ -71,7 +143,7 @@ Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
|
||||
{
|
||||
return efYenc;
|
||||
}
|
||||
|
||||
|
||||
if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
|
||||
{
|
||||
return efUx;
|
||||
@@ -79,18 +151,18 @@ Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
|
||||
|
||||
if (!strncmp(buffer, "begin ", 6))
|
||||
{
|
||||
bool bOK = true;
|
||||
bool ok = true;
|
||||
buffer += 6; //strlen("begin ")
|
||||
while (*buffer && *buffer != ' ')
|
||||
{
|
||||
char ch = *buffer++;
|
||||
if (ch < '0' || ch > '7')
|
||||
{
|
||||
bOK = false;
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bOK)
|
||||
if (ok)
|
||||
{
|
||||
return efUx;
|
||||
}
|
||||
@@ -99,185 +171,148 @@ Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
|
||||
return efUnknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* YDecoder: fast implementation of yEnc-Decoder
|
||||
*/
|
||||
|
||||
YDecoder::YDecoder()
|
||||
void Decoder::ProcessYenc(char* buffer, int len)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void YDecoder::Clear()
|
||||
{
|
||||
Decoder::Clear();
|
||||
|
||||
m_bBody = false;
|
||||
m_bBegin = false;
|
||||
m_bPart = false;
|
||||
m_bEnd = false;
|
||||
m_bCrc = false;
|
||||
m_lExpectedCRC = 0;
|
||||
m_lCalculatedCRC = 0xFFFFFFFF;
|
||||
m_iBegin = 0;
|
||||
m_iEnd = 0;
|
||||
m_iSize = 0;
|
||||
m_iEndSize = 0;
|
||||
m_bCrcCheck = false;
|
||||
}
|
||||
|
||||
int YDecoder::DecodeBuffer(char* buffer, int len)
|
||||
{
|
||||
if (m_bBody && !m_bEnd)
|
||||
if (!strncmp(buffer, "=ybegin ", 8))
|
||||
{
|
||||
if (!strncmp(buffer, "=yend ", 6))
|
||||
m_begin = true;
|
||||
char* pb = strstr(buffer, " name=");
|
||||
if (pb)
|
||||
{
|
||||
m_bEnd = true;
|
||||
char* pb = strstr(buffer, m_bPart ? " pcrc32=" : " crc32=");
|
||||
if (pb)
|
||||
{
|
||||
m_bCrc = true;
|
||||
pb += 7 + (int)m_bPart; //=strlen(" crc32=") or strlen(" pcrc32=")
|
||||
m_lExpectedCRC = strtoul(pb, NULL, 16);
|
||||
}
|
||||
pb = strstr(buffer, " size=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_iEndSize = (long long)atoll(pb);
|
||||
}
|
||||
return 0;
|
||||
pb += 6; //=strlen(" name=")
|
||||
char* pe;
|
||||
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++);
|
||||
m_articleFilename = WebUtil::Latin1ToUtf8(CString(pb, (int)(pe - pb)));
|
||||
}
|
||||
|
||||
char* iptr = buffer;
|
||||
char* optr = buffer;
|
||||
while (true)
|
||||
pb = strstr(buffer, " size=");
|
||||
if (pb)
|
||||
{
|
||||
switch (*iptr)
|
||||
{
|
||||
case '=': //escape-sequence
|
||||
iptr++;
|
||||
*optr = *iptr - 64 - 42;
|
||||
optr++;
|
||||
break;
|
||||
case '\n': // ignored char
|
||||
case '\r': // ignored char
|
||||
break;
|
||||
case '\0':
|
||||
goto BreakLoop;
|
||||
default: // normal char
|
||||
*optr = *iptr - 42;
|
||||
optr++;
|
||||
break;
|
||||
}
|
||||
iptr++;
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_size = (int64)atoll(pb);
|
||||
}
|
||||
BreakLoop:
|
||||
|
||||
if (m_bCrcCheck)
|
||||
m_part = strstr(buffer, " part=");
|
||||
if (!m_part)
|
||||
{
|
||||
m_lCalculatedCRC = Util::Crc32m(m_lCalculatedCRC, (unsigned char *)buffer, (unsigned int)(optr - buffer));
|
||||
}
|
||||
return optr - buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_bPart && !strncmp(buffer, "=ybegin ", 8))
|
||||
{
|
||||
m_bBegin = true;
|
||||
char* pb = strstr(buffer, " name=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" name=")
|
||||
char* pe;
|
||||
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = (char*)malloc(pe - pb + 1);
|
||||
strncpy(m_szArticleFilename, pb, pe - pb);
|
||||
m_szArticleFilename[pe - pb] = '\0';
|
||||
}
|
||||
pb = strstr(buffer, " size=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_iSize = (long long)atoll(pb);
|
||||
}
|
||||
m_bPart = strstr(buffer, " part=");
|
||||
if (!m_bPart)
|
||||
{
|
||||
m_bBody = true;
|
||||
m_iBegin = 1;
|
||||
m_iEnd = m_iSize;
|
||||
}
|
||||
}
|
||||
else if (m_bPart && !strncmp(buffer, "=ypart ", 7))
|
||||
{
|
||||
m_bPart = true;
|
||||
m_bBody = true;
|
||||
char* pb = strstr(buffer, " begin=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 7; //=strlen(" begin=")
|
||||
m_iBegin = (long long)atoll(pb);
|
||||
}
|
||||
pb = strstr(buffer, " end=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 5; //=strlen(" end=")
|
||||
m_iEnd = (long long)atoll(pb);
|
||||
}
|
||||
m_body = true;
|
||||
m_beginPos = 1;
|
||||
m_endPos = m_size;
|
||||
}
|
||||
}
|
||||
else if (!strncmp(buffer, "=ypart ", 7))
|
||||
{
|
||||
m_part = true;
|
||||
m_body = true;
|
||||
char* pb = strstr(buffer, " begin=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 7; //=strlen(" begin=")
|
||||
m_beginPos = (int64)atoll(pb);
|
||||
}
|
||||
pb = strstr(buffer, " end=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 5; //=strlen(" end=")
|
||||
m_endPos = (int64)atoll(pb);
|
||||
}
|
||||
}
|
||||
else if (!strncmp(buffer, "=yend ", 6))
|
||||
{
|
||||
m_end = true;
|
||||
char* pb = strstr(buffer, m_part ? " pcrc32=" : " crc32=");
|
||||
if (pb)
|
||||
{
|
||||
m_crc = true;
|
||||
pb += 7 + (int)m_part; //=strlen(" crc32=") or strlen(" pcrc32=")
|
||||
m_expectedCRC = strtoul(pb, nullptr, 16);
|
||||
}
|
||||
pb = strstr(buffer, " size=");
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_endSize = (int64)atoll(pb);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Decoder::EStatus YDecoder::Check()
|
||||
int Decoder::DecodeYenc(char* buffer, char* outbuf, int len)
|
||||
{
|
||||
m_lCalculatedCRC ^= 0xFFFFFFFF;
|
||||
const unsigned char* src = (unsigned char*)buffer;
|
||||
unsigned char* dst = (unsigned char*)outbuf;
|
||||
|
||||
debug("Expected crc32=%x", m_lExpectedCRC);
|
||||
debug("Calculated crc32=%x", m_lCalculatedCRC);
|
||||
int endseq = YEncode::decode(&src, &dst, len, (YEncode::YencDecoderState*)&m_state);
|
||||
int outlen = (int)((char*)dst - outbuf);
|
||||
|
||||
if (!m_bBegin)
|
||||
// endseq:
|
||||
// 0: no end sequence found
|
||||
// 1: \r\n=y sequence found, src points to byte after 'y'
|
||||
// 2: \r\n.\r\n sequence found, src points to byte after last '\n'
|
||||
if (endseq != 0)
|
||||
{
|
||||
return eNoBinaryData;
|
||||
}
|
||||
else if (!m_bEnd)
|
||||
{
|
||||
return eArticleIncomplete;
|
||||
}
|
||||
else if (!m_bPart && m_iSize != m_iEndSize)
|
||||
{
|
||||
return eInvalidSize;
|
||||
}
|
||||
else if (m_bCrcCheck && m_bCrc && (m_lExpectedCRC != m_lCalculatedCRC))
|
||||
{
|
||||
return eCrcError;
|
||||
// switch back to line mode to process '=yend'- or eof- marker
|
||||
m_lineBuf.SetLength(0);
|
||||
m_lineBuf.Append(endseq == 1 ? "=y" : ".\r\n");
|
||||
int rem = len - (int)((const char*)src - buffer);
|
||||
if (rem > 0)
|
||||
{
|
||||
m_lineBuf.Append((const char*)src, rem);
|
||||
}
|
||||
m_body = false;
|
||||
}
|
||||
|
||||
return eFinished;
|
||||
if (m_crcCheck)
|
||||
{
|
||||
m_crc32.Append((uchar*)outbuf, (uint32)outlen);
|
||||
}
|
||||
|
||||
m_outSize += outlen;
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UDecoder: supports UU encoding formats
|
||||
*/
|
||||
|
||||
UDecoder::UDecoder()
|
||||
Decoder::EStatus Decoder::Check()
|
||||
{
|
||||
switch (m_format)
|
||||
{
|
||||
case efYenc:
|
||||
return CheckYenc();
|
||||
|
||||
case efUx:
|
||||
return CheckUx();
|
||||
|
||||
default:
|
||||
return dsUnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
void UDecoder::Clear()
|
||||
Decoder::EStatus Decoder::CheckYenc()
|
||||
{
|
||||
Decoder::Clear();
|
||||
m_calculatedCRC = m_crc32.Finish();
|
||||
|
||||
m_bBody = false;
|
||||
m_bEnd = false;
|
||||
debug("Expected crc32=%x", m_expectedCRC);
|
||||
debug("Calculated crc32=%x", m_calculatedCRC);
|
||||
|
||||
if (!m_begin)
|
||||
{
|
||||
return dsNoBinaryData;
|
||||
}
|
||||
else if (!m_end)
|
||||
{
|
||||
return dsArticleIncomplete;
|
||||
}
|
||||
else if ((!m_part && m_size != m_endSize) || (m_endSize != m_outSize))
|
||||
{
|
||||
return dsInvalidSize;
|
||||
}
|
||||
else if (m_crcCheck && m_crc && (m_expectedCRC != m_calculatedCRC))
|
||||
{
|
||||
return dsCrcError;
|
||||
}
|
||||
|
||||
return dsFinished;
|
||||
}
|
||||
|
||||
/* DecodeBuffer-function uses portions of code from tool UUDECODE by Clem Dye
|
||||
|
||||
/* DecodeUx-function uses portions of code from tool UUDECODE by Clem Dye
|
||||
* UUDECODE.c (http://www.bastet.com/uue.zip)
|
||||
* Copyright (C) 1998 Clem Dye
|
||||
*
|
||||
@@ -286,9 +321,9 @@ void UDecoder::Clear()
|
||||
|
||||
#define UU_DECODE_CHAR(c) (c == '`' ? 0 : (((c) - ' ') & 077))
|
||||
|
||||
int UDecoder::DecodeBuffer(char* buffer, int len)
|
||||
int Decoder::DecodeUx(char* buffer, int len)
|
||||
{
|
||||
if (!m_bBody)
|
||||
if (!m_body)
|
||||
{
|
||||
if (!strncmp(buffer, "begin ", 6))
|
||||
{
|
||||
@@ -296,35 +331,32 @@ int UDecoder::DecodeBuffer(char* buffer, int len)
|
||||
pb += 6; //strlen("begin ")
|
||||
|
||||
// skip file-permissions
|
||||
for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ;
|
||||
for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ;
|
||||
pb++;
|
||||
|
||||
// extracting filename
|
||||
char* pe;
|
||||
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = (char*)malloc(pe - pb + 1);
|
||||
strncpy(m_szArticleFilename, pb, pe - pb);
|
||||
m_szArticleFilename[pe - pb] = '\0';
|
||||
m_articleFilename = WebUtil::Latin1ToUtf8(CString(pb, (int)(pe - pb)));
|
||||
|
||||
m_bBody = true;
|
||||
m_body = true;
|
||||
return 0;
|
||||
}
|
||||
else if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
|
||||
{
|
||||
m_bBody = true;
|
||||
m_body = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bBody && (!strncmp(buffer, "end ", 4) || *buffer == '`'))
|
||||
if (m_body && (!strncmp(buffer, "end ", 4) || *buffer == '`'))
|
||||
{
|
||||
m_bEnd = true;
|
||||
m_end = true;
|
||||
}
|
||||
|
||||
if (m_bBody && !m_bEnd)
|
||||
if (m_body && !m_end)
|
||||
{
|
||||
int iEffLen = UU_DECODE_CHAR(buffer[0]);
|
||||
if (iEffLen > len)
|
||||
int effLen = UU_DECODE_CHAR(buffer[0]);
|
||||
if (effLen > len)
|
||||
{
|
||||
// error;
|
||||
return 0;
|
||||
@@ -332,39 +364,83 @@ int UDecoder::DecodeBuffer(char* buffer, int len)
|
||||
|
||||
char* iptr = buffer;
|
||||
char* optr = buffer;
|
||||
for (++iptr; iEffLen > 0; iptr += 4, iEffLen -= 3)
|
||||
for (++iptr; effLen > 0; iptr += 4, effLen -= 3)
|
||||
{
|
||||
if (iEffLen >= 3)
|
||||
if (effLen >= 3)
|
||||
{
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[2]) << 6 | UU_DECODE_CHAR (iptr[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iEffLen >= 1)
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
if (effLen >= 2)
|
||||
{
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
}
|
||||
if (iEffLen >= 2)
|
||||
{
|
||||
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return optr - buffer;
|
||||
return (int)(optr - buffer);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Decoder::EStatus UDecoder::Check()
|
||||
Decoder::EStatus Decoder::CheckUx()
|
||||
{
|
||||
if (!m_bBody)
|
||||
if (!m_body)
|
||||
{
|
||||
return eNoBinaryData;
|
||||
return dsNoBinaryData;
|
||||
}
|
||||
|
||||
return eFinished;
|
||||
return dsFinished;
|
||||
}
|
||||
|
||||
void Decoder::ProcessRaw(char* buffer, int len)
|
||||
{
|
||||
switch (m_state)
|
||||
{
|
||||
case 1:
|
||||
m_eof = len >= 4 && buffer[0] == '\n' &&
|
||||
buffer[1] == '.' && buffer[2] == '\r' && buffer[3] == '\n';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
m_eof = len >= 3 && buffer[0] == '.' && buffer[1] == '\r' && buffer[2] == '\n';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
m_eof = len >= 2 && buffer[0] == '\r' && buffer[1] == '\n';
|
||||
break;
|
||||
|
||||
case 4:
|
||||
m_eof = len >= 1 && buffer[0] == '\n';
|
||||
break;
|
||||
}
|
||||
|
||||
m_eof |= len >= 5 && strstr(buffer, "\r\n.\r\n");
|
||||
|
||||
if (len >= 4 && buffer[len-4] == '\r' && buffer[len-3] == '\n' &&
|
||||
buffer[len-2] == '.' && buffer[len-1] == '\r')
|
||||
{
|
||||
m_state = 4;
|
||||
}
|
||||
else if (len >= 3 && buffer[len-3] == '\r' && buffer[len-2] == '\n' && buffer[len-1] == '.')
|
||||
{
|
||||
m_state = 3;
|
||||
}
|
||||
else if (len >= 2 && buffer[len-2] == '\r' && buffer[len-1] == '\n')
|
||||
{
|
||||
m_state = 2;
|
||||
}
|
||||
else if (len >= 1 && buffer[len-1] == '\r')
|
||||
{
|
||||
m_state = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,29 +14,27 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Util.h"
|
||||
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
enum EStatus
|
||||
{
|
||||
eUnknownError,
|
||||
eFinished,
|
||||
eArticleIncomplete,
|
||||
eCrcError,
|
||||
eInvalidSize,
|
||||
eNoBinaryData
|
||||
dsUnknownError,
|
||||
dsFinished,
|
||||
dsArticleIncomplete,
|
||||
dsCrcError,
|
||||
dsInvalidSize,
|
||||
dsNoBinaryData
|
||||
};
|
||||
|
||||
enum EFormat
|
||||
@@ -46,61 +44,50 @@ public:
|
||||
efUx,
|
||||
};
|
||||
|
||||
static const char* FormatNames[];
|
||||
Decoder();
|
||||
EStatus Check();
|
||||
void Clear();
|
||||
int DecodeBuffer(char* buffer, int len);
|
||||
void SetCrcCheck(bool crcCheck) { m_crcCheck = crcCheck; }
|
||||
void SetRawMode(bool rawMode) { m_rawMode = rawMode; }
|
||||
EFormat GetFormat() { return m_format; }
|
||||
int64 GetBeginPos() { return m_beginPos; }
|
||||
int64 GetEndPos() { return m_endPos; }
|
||||
int64 GetSize() { return m_size; }
|
||||
uint32 GetExpectedCrc() { return m_expectedCRC; }
|
||||
uint32 GetCalculatedCrc() { return m_calculatedCRC; }
|
||||
bool GetEof() { return m_eof; }
|
||||
const char* GetArticleFilename() { return m_articleFilename; }
|
||||
|
||||
protected:
|
||||
char* m_szArticleFilename;
|
||||
private:
|
||||
EFormat m_format = efUnknown;
|
||||
bool m_begin;
|
||||
bool m_part;
|
||||
bool m_body;
|
||||
bool m_end;
|
||||
bool m_crc;
|
||||
uint32 m_expectedCRC;
|
||||
uint32 m_calculatedCRC;
|
||||
int64 m_beginPos;
|
||||
int64 m_endPos;
|
||||
int64 m_size;
|
||||
int64 m_endSize;
|
||||
int64 m_outSize;
|
||||
bool m_eof;
|
||||
bool m_crcCheck;
|
||||
char m_state;
|
||||
bool m_rawMode = false;
|
||||
CString m_articleFilename;
|
||||
StringBuilder m_lineBuf;
|
||||
Crc32 m_crc32;
|
||||
|
||||
public:
|
||||
Decoder();
|
||||
virtual ~Decoder();
|
||||
virtual EStatus Check() = 0;
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len) = 0;
|
||||
const char* GetArticleFilename() { return m_szArticleFilename; }
|
||||
static EFormat DetectFormat(const char* buffer, int len);
|
||||
};
|
||||
|
||||
class YDecoder: public Decoder
|
||||
{
|
||||
protected:
|
||||
bool m_bBegin;
|
||||
bool m_bPart;
|
||||
bool m_bBody;
|
||||
bool m_bEnd;
|
||||
bool m_bCrc;
|
||||
unsigned long m_lExpectedCRC;
|
||||
unsigned long m_lCalculatedCRC;
|
||||
long long m_iBegin;
|
||||
long long m_iEnd;
|
||||
long long m_iSize;
|
||||
long long m_iEndSize;
|
||||
bool m_bCrcCheck;
|
||||
|
||||
public:
|
||||
YDecoder();
|
||||
virtual EStatus Check();
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len);
|
||||
void SetCrcCheck(bool bCrcCheck) { m_bCrcCheck = bCrcCheck; }
|
||||
long long GetBegin() { return m_iBegin; }
|
||||
long long GetEnd() { return m_iEnd; }
|
||||
long long GetSize() { return m_iSize; }
|
||||
unsigned long GetExpectedCrc() { return m_lExpectedCRC; }
|
||||
unsigned long GetCalculatedCrc() { return m_lCalculatedCRC; }
|
||||
};
|
||||
|
||||
class UDecoder: public Decoder
|
||||
{
|
||||
private:
|
||||
bool m_bBody;
|
||||
bool m_bEnd;
|
||||
|
||||
public:
|
||||
UDecoder();
|
||||
virtual EStatus Check();
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len);
|
||||
EFormat DetectFormat(const char* buffer, int len);
|
||||
void ProcessYenc(char* buffer, int len);
|
||||
int DecodeYenc(char* buffer, char* outbuf, int len);
|
||||
EStatus CheckYenc();
|
||||
int DecodeUx(char* buffer, int len);
|
||||
EStatus CheckUx();
|
||||
void ProcessRaw(char* buffer, int len);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Log.h"
|
||||
#include "NNTPConnection.h"
|
||||
#include "Connection.h"
|
||||
#include "NewsServer.h"
|
||||
|
||||
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
|
||||
|
||||
NNTPConnection::NNTPConnection(NewsServer* pNewsServer) : Connection(pNewsServer->GetHost(), pNewsServer->GetPort(), pNewsServer->GetTLS())
|
||||
{
|
||||
m_pNewsServer = pNewsServer;
|
||||
m_szActiveGroup = NULL;
|
||||
m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE);
|
||||
m_bAuthError = false;
|
||||
SetCipher(pNewsServer->GetCipher());
|
||||
}
|
||||
|
||||
NNTPConnection::~NNTPConnection()
|
||||
{
|
||||
free(m_szActiveGroup);
|
||||
free(m_szLineBuf);
|
||||
}
|
||||
|
||||
const char* NNTPConnection::Request(const char* req)
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
m_bAuthError = false;
|
||||
|
||||
WriteLine(req);
|
||||
|
||||
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
|
||||
|
||||
if (!answer)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!strncmp(answer, "480", 3))
|
||||
{
|
||||
debug("%s requested authorization", GetHost());
|
||||
|
||||
if (!Authenticate())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//try again
|
||||
WriteLine(req);
|
||||
answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
bool NNTPConnection::Authenticate()
|
||||
{
|
||||
if (strlen(m_pNewsServer->GetUser()) == 0 || strlen(m_pNewsServer->GetPassword()) == 0)
|
||||
{
|
||||
error("%c%s (%s) requested authorization but username/password are not set in settings",
|
||||
toupper(m_pNewsServer->GetName()[0]), m_pNewsServer->GetName() + 1, m_pNewsServer->GetHost());
|
||||
m_bAuthError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bAuthError = !AuthInfoUser(0);
|
||||
return !m_bAuthError;
|
||||
}
|
||||
|
||||
bool NNTPConnection::AuthInfoUser(int iRecur)
|
||||
{
|
||||
if (iRecur > 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char tmp[1024];
|
||||
snprintf(tmp, 1024, "AUTHINFO USER %s\r\n", m_pNewsServer->GetUser());
|
||||
tmp[1024-1] = '\0';
|
||||
|
||||
WriteLine(tmp);
|
||||
|
||||
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strncmp(answer, "281", 3))
|
||||
{
|
||||
debug("Authorization for %s successful", GetHost());
|
||||
return true;
|
||||
}
|
||||
else if (!strncmp(answer, "381", 3))
|
||||
{
|
||||
return AuthInfoPass(++iRecur);
|
||||
}
|
||||
else if (!strncmp(answer, "480", 3))
|
||||
{
|
||||
return AuthInfoUser(++iRecur);
|
||||
}
|
||||
|
||||
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
|
||||
|
||||
if (GetStatus() != csCancelled)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NNTPConnection::AuthInfoPass(int iRecur)
|
||||
{
|
||||
if (iRecur > 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char tmp[1024];
|
||||
snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", m_pNewsServer->GetPassword());
|
||||
tmp[1024-1] = '\0';
|
||||
|
||||
WriteLine(tmp);
|
||||
|
||||
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL);
|
||||
return false;
|
||||
}
|
||||
else if (!strncmp(answer, "2", 1))
|
||||
{
|
||||
debug("Authorization for %s successful", GetHost());
|
||||
return true;
|
||||
}
|
||||
else if (!strncmp(answer, "381", 3))
|
||||
{
|
||||
return AuthInfoPass(++iRecur);
|
||||
}
|
||||
|
||||
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
|
||||
|
||||
if (GetStatus() != csCancelled)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* NNTPConnection::JoinGroup(const char* grp)
|
||||
{
|
||||
if (m_szActiveGroup && !strcmp(m_szActiveGroup, grp))
|
||||
{
|
||||
// already in group
|
||||
strcpy(m_szLineBuf, "211 ");
|
||||
return m_szLineBuf;
|
||||
}
|
||||
|
||||
char tmp[1024];
|
||||
snprintf(tmp, 1024, "GROUP %s\r\n", grp);
|
||||
tmp[1024-1] = '\0';
|
||||
|
||||
const char* answer = Request(tmp);
|
||||
|
||||
if (answer && !strncmp(answer, "2", 1))
|
||||
{
|
||||
debug("Changed group to %s on %s", grp, GetHost());
|
||||
free(m_szActiveGroup);
|
||||
m_szActiveGroup = strdup(grp);
|
||||
}
|
||||
else
|
||||
{
|
||||
debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
bool NNTPConnection::Connect()
|
||||
{
|
||||
debug("Opening connection to %s", GetHost());
|
||||
|
||||
if (m_eStatus == csConnected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Connection::Connect())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
|
||||
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Connection to server%i (%s) failed: Connection closed by remote host", NULL);
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(answer, "2", 1))
|
||||
{
|
||||
ReportErrorAnswer("Connection to server%i (%s) failed (Answer: %s)", answer);
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((strlen(m_pNewsServer->GetUser()) > 0 && strlen(m_pNewsServer->GetPassword()) > 0) &&
|
||||
!Authenticate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
debug("Connection to %s established", GetHost());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NNTPConnection::Disconnect()
|
||||
{
|
||||
if (m_eStatus == csConnected)
|
||||
{
|
||||
Request("quit\r\n");
|
||||
free(m_szActiveGroup);
|
||||
m_szActiveGroup = NULL;
|
||||
}
|
||||
return Connection::Disconnect();
|
||||
}
|
||||
|
||||
void NNTPConnection::ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer)
|
||||
{
|
||||
char szErrStr[1024];
|
||||
snprintf(szErrStr, 1024, szMsgPrefix, m_pNewsServer->GetID(), m_pNewsServer->GetHost(), szAnswer);
|
||||
szErrStr[1024-1] = '\0';
|
||||
|
||||
ReportError(szErrStr, NULL, false, 0);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2008 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NNTPCONNECTION_H
|
||||
#define NNTPCONNECTION_H
|
||||
|
||||
#include "NewsServer.h"
|
||||
#include "Connection.h"
|
||||
|
||||
class NNTPConnection : public Connection
|
||||
{
|
||||
private:
|
||||
NewsServer* m_pNewsServer;
|
||||
char* m_szActiveGroup;
|
||||
char* m_szLineBuf;
|
||||
bool m_bAuthError;
|
||||
|
||||
void Clear();
|
||||
void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer);
|
||||
bool Authenticate();
|
||||
bool AuthInfoUser(int iRecur);
|
||||
bool AuthInfoPass(int iRecur);
|
||||
|
||||
public:
|
||||
NNTPConnection(NewsServer* pNewsServer);
|
||||
virtual ~NNTPConnection();
|
||||
virtual bool Connect();
|
||||
virtual bool Disconnect();
|
||||
NewsServer* GetNewsServer() { return m_pNewsServer; }
|
||||
const char* Request(const char* req);
|
||||
const char* JoinGroup(const char* grp);
|
||||
bool GetAuthError() { return m_bAuthError; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file if part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,66 +15,23 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NewsServer.h"
|
||||
|
||||
NewsServer::NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
|
||||
const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS,
|
||||
const char* szCipher, int iMaxConnections, int iLevel, int iGroup)
|
||||
NewsServer::NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
|
||||
const char* user, const char* pass, bool joinGroup, bool tls, const char* cipher,
|
||||
int maxConnections, int retention, int level, int group, bool optional) :
|
||||
m_id(id), m_active(active), m_name(name), m_host(host ? host : ""), m_port(port), m_ipVersion(ipVersion),
|
||||
m_user(user ? user : ""), m_password(pass ? pass : ""), m_joinGroup(joinGroup), m_tls(tls),
|
||||
m_cipher(cipher ? cipher : ""), m_maxConnections(maxConnections), m_retention(retention),
|
||||
m_level(level), m_normLevel(level), m_group(group), m_optional(optional)
|
||||
{
|
||||
m_iID = iID;
|
||||
m_iStateID = 0;
|
||||
m_bActive = bActive;
|
||||
m_iPort = iPort;
|
||||
m_iLevel = iLevel;
|
||||
m_iNormLevel = iLevel;
|
||||
m_iGroup = iGroup;
|
||||
m_iMaxConnections = iMaxConnections;
|
||||
m_bJoinGroup = bJoinGroup;
|
||||
m_bTLS = bTLS;
|
||||
m_szHost = strdup(szHost ? szHost : "");
|
||||
m_szUser = strdup(szUser ? szUser : "");
|
||||
m_szPassword = strdup(szPass ? szPass : "");
|
||||
m_szCipher = strdup(szCipher ? szCipher : "");
|
||||
|
||||
if (szName && strlen(szName) > 0)
|
||||
if (m_name.Empty())
|
||||
{
|
||||
m_szName = strdup(szName);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_szName = (char*)malloc(20);
|
||||
snprintf(m_szName, 20, "server%i", iID);
|
||||
m_szName[20-1] = '\0';
|
||||
m_name.Format("server%i", id);
|
||||
}
|
||||
}
|
||||
|
||||
NewsServer::~NewsServer()
|
||||
{
|
||||
free(m_szName);
|
||||
free(m_szHost);
|
||||
free(m_szUser);
|
||||
free(m_szPassword);
|
||||
free(m_szCipher);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* This file if part of nzbget
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -27,52 +27,61 @@
|
||||
#ifndef NEWSSERVER_H
|
||||
#define NEWSSERVER_H
|
||||
|
||||
#include <vector>
|
||||
#include "NString.h"
|
||||
|
||||
class NewsServer
|
||||
{
|
||||
private:
|
||||
int m_iID;
|
||||
int m_iStateID;
|
||||
bool m_bActive;
|
||||
char* m_szName;
|
||||
int m_iGroup;
|
||||
char* m_szHost;
|
||||
int m_iPort;
|
||||
char* m_szUser;
|
||||
char* m_szPassword;
|
||||
int m_iMaxConnections;
|
||||
int m_iLevel;
|
||||
int m_iNormLevel;
|
||||
bool m_bJoinGroup;
|
||||
bool m_bTLS;
|
||||
char* m_szCipher;
|
||||
|
||||
public:
|
||||
NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
|
||||
const char* szUser, const char* szPass, bool bJoinGroup,
|
||||
bool bTLS, const char* szCipher, int iMaxConnections, int iLevel, int iGroup);
|
||||
~NewsServer();
|
||||
int GetID() { return m_iID; }
|
||||
int GetStateID() { return m_iStateID; }
|
||||
void SetStateID(int iStateID) { m_iStateID = iStateID; }
|
||||
bool GetActive() { return m_bActive; }
|
||||
void SetActive(bool bActive) { m_bActive = bActive; }
|
||||
const char* GetName() { return m_szName; }
|
||||
int GetGroup() { return m_iGroup; }
|
||||
const char* GetHost() { return m_szHost; }
|
||||
int GetPort() { return m_iPort; }
|
||||
const char* GetUser() { return m_szUser; }
|
||||
const char* GetPassword() { return m_szPassword; }
|
||||
int GetMaxConnections() { return m_iMaxConnections; }
|
||||
int GetLevel() { return m_iLevel; }
|
||||
int GetNormLevel() { return m_iNormLevel; }
|
||||
void SetNormLevel(int iLevel) { m_iNormLevel = iLevel; }
|
||||
int GetJoinGroup() { return m_bJoinGroup; }
|
||||
bool GetTLS() { return m_bTLS; }
|
||||
const char* GetCipher() { return m_szCipher; }
|
||||
NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
|
||||
const char* user, const char* pass, bool joinGroup,
|
||||
bool tls, const char* cipher, int maxConnections, int retention,
|
||||
int level, int group, bool optional);
|
||||
int GetId() { return m_id; }
|
||||
int GetStateId() { return m_stateId; }
|
||||
void SetStateId(int stateId) { m_stateId = stateId; }
|
||||
bool GetActive() { return m_active; }
|
||||
void SetActive(bool active) { m_active = active; }
|
||||
const char* GetName() { return m_name; }
|
||||
int GetGroup() { return m_group; }
|
||||
const char* GetHost() { return m_host; }
|
||||
int GetPort() { return m_port; }
|
||||
int GetIpVersion() { return m_ipVersion; }
|
||||
const char* GetUser() { return m_user; }
|
||||
const char* GetPassword() { return m_password; }
|
||||
int GetMaxConnections() { return m_maxConnections; }
|
||||
int GetLevel() { return m_level; }
|
||||
int GetNormLevel() { return m_normLevel; }
|
||||
void SetNormLevel(int level) { m_normLevel = level; }
|
||||
int GetJoinGroup() { return m_joinGroup; }
|
||||
bool GetTls() { return m_tls; }
|
||||
const char* GetCipher() { return m_cipher; }
|
||||
int GetRetention() { return m_retention; }
|
||||
bool GetOptional() { return m_optional; }
|
||||
time_t GetBlockTime() { return m_blockTime; }
|
||||
void SetBlockTime(time_t blockTime) { m_blockTime = blockTime; }
|
||||
|
||||
private:
|
||||
int m_id;
|
||||
int m_stateId = 0;
|
||||
bool m_active;
|
||||
CString m_name;
|
||||
CString m_host;
|
||||
int m_port;
|
||||
int m_ipVersion;
|
||||
CString m_user;
|
||||
CString m_password;
|
||||
bool m_joinGroup;
|
||||
bool m_tls;
|
||||
CString m_cipher;
|
||||
int m_maxConnections;
|
||||
int m_retention;
|
||||
int m_level;
|
||||
int m_normLevel;
|
||||
int m_group;
|
||||
bool m_optional = false;
|
||||
time_t m_blockTime = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<NewsServer*> Servers;
|
||||
typedef std::vector<std::unique_ptr<NewsServer>> Servers;
|
||||
|
||||
#endif
|
||||
|
||||
240
daemon/nntp/NntpConnection.cpp
Normal file
240
daemon/nntp/NntpConnection.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Log.h"
|
||||
#include "NntpConnection.h"
|
||||
#include "Connection.h"
|
||||
#include "NewsServer.h"
|
||||
|
||||
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
|
||||
|
||||
NntpConnection::NntpConnection(NewsServer* newsServer) :
|
||||
Connection(newsServer->GetHost(), newsServer->GetPort(), newsServer->GetTls()), m_newsServer(newsServer)
|
||||
{
|
||||
m_lineBuf.Reserve(CONNECTION_LINEBUFFER_SIZE);
|
||||
SetCipher(newsServer->GetCipher());
|
||||
SetIPVersion(newsServer->GetIpVersion() == 4 ? Connection::ipV4 :
|
||||
newsServer->GetIpVersion() == 6 ? Connection::ipV6 : Connection::ipAuto);
|
||||
}
|
||||
|
||||
const char* NntpConnection::Request(const char* req)
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_authError = false;
|
||||
|
||||
WriteLine(req);
|
||||
|
||||
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
|
||||
|
||||
if (!answer)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!strncmp(answer, "480", 3))
|
||||
{
|
||||
debug("%s requested authorization", GetHost());
|
||||
|
||||
if (!Authenticate())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//try again
|
||||
WriteLine(req);
|
||||
answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
bool NntpConnection::Authenticate()
|
||||
{
|
||||
if (strlen(m_newsServer->GetUser()) == 0 || strlen(m_newsServer->GetPassword()) == 0)
|
||||
{
|
||||
ReportError("Could not connect to %s: server requested authorization but username/password are not set in settings",
|
||||
m_newsServer->GetHost(), false, 0);
|
||||
m_authError = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_authError = !AuthInfoUser(0);
|
||||
return !m_authError;
|
||||
}
|
||||
|
||||
bool NntpConnection::AuthInfoUser(int recur)
|
||||
{
|
||||
if (recur > 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteLine(BString<1024>("AUTHINFO USER %s\r\n", m_newsServer->GetUser()));
|
||||
|
||||
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for %s (%s) failed: Connection closed by remote host", nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!strncmp(answer, "281", 3))
|
||||
{
|
||||
debug("Authorization for %s successful", GetHost());
|
||||
return true;
|
||||
}
|
||||
else if (!strncmp(answer, "381", 3))
|
||||
{
|
||||
return AuthInfoPass(++recur);
|
||||
}
|
||||
else if (!strncmp(answer, "480", 3))
|
||||
{
|
||||
return AuthInfoUser(++recur);
|
||||
}
|
||||
|
||||
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
|
||||
|
||||
if (GetStatus() != csCancelled)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NntpConnection::AuthInfoPass(int recur)
|
||||
{
|
||||
if (recur > 10)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteLine(BString<1024>("AUTHINFO PASS %s\r\n", m_newsServer->GetPassword()));
|
||||
|
||||
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Authorization failed for %s (%s): Connection closed by remote host", nullptr);
|
||||
return false;
|
||||
}
|
||||
else if (!strncmp(answer, "2", 1))
|
||||
{
|
||||
debug("Authorization for %s successful", GetHost());
|
||||
return true;
|
||||
}
|
||||
else if (!strncmp(answer, "381", 3))
|
||||
{
|
||||
return AuthInfoPass(++recur);
|
||||
}
|
||||
|
||||
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
|
||||
|
||||
if (GetStatus() != csCancelled)
|
||||
{
|
||||
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* NntpConnection::JoinGroup(const char* grp)
|
||||
{
|
||||
if (!m_activeGroup.Empty() && !strcmp(m_activeGroup, grp))
|
||||
{
|
||||
// already in group
|
||||
strcpy(m_lineBuf, "211 ");
|
||||
return m_lineBuf;
|
||||
}
|
||||
|
||||
const char* answer = Request(BString<1024>("GROUP %s\r\n", grp));
|
||||
|
||||
if (answer && !strncmp(answer, "2", 1))
|
||||
{
|
||||
debug("Changed group to %s on %s", grp, GetHost());
|
||||
m_activeGroup = grp;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer);
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
bool NntpConnection::Connect()
|
||||
{
|
||||
debug("Opening connection to %s", GetHost());
|
||||
|
||||
if (m_status == csConnected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Connection::Connect())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
|
||||
|
||||
if (!answer)
|
||||
{
|
||||
ReportErrorAnswer("Connection to %s (%s) failed: Connection closed by remote host", nullptr);
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp(answer, "2", 1))
|
||||
{
|
||||
ReportErrorAnswer("Connection to %s (%s) failed: %s", answer);
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((strlen(m_newsServer->GetUser()) > 0 && strlen(m_newsServer->GetPassword()) > 0) &&
|
||||
!Authenticate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
debug("Connection to %s established", GetHost());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NntpConnection::Disconnect()
|
||||
{
|
||||
if (m_status == csConnected)
|
||||
{
|
||||
Request("quit\r\n");
|
||||
m_activeGroup = nullptr;
|
||||
}
|
||||
return Connection::Disconnect();
|
||||
}
|
||||
|
||||
void NntpConnection::ReportErrorAnswer(const char* msgPrefix, const char* answer)
|
||||
{
|
||||
BString<1024> errStr(msgPrefix, m_newsServer->GetName(), m_newsServer->GetHost(), answer);
|
||||
ReportError(errStr, nullptr, false, 0);
|
||||
}
|
||||
53
daemon/nntp/NntpConnection.h
Normal file
53
daemon/nntp/NntpConnection.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NNTPCONNECTION_H
|
||||
#define NNTPCONNECTION_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "NewsServer.h"
|
||||
#include "Connection.h"
|
||||
|
||||
class NntpConnection : public Connection
|
||||
{
|
||||
public:
|
||||
NntpConnection(NewsServer* newsServer);
|
||||
virtual bool Connect();
|
||||
virtual bool Disconnect();
|
||||
NewsServer* GetNewsServer() { return m_newsServer; }
|
||||
const char* Request(const char* req);
|
||||
const char* JoinGroup(const char* grp);
|
||||
bool GetAuthError() { return m_authError; }
|
||||
|
||||
private:
|
||||
NewsServer* m_newsServer;
|
||||
CString m_activeGroup;
|
||||
CharBuffer m_lineBuf;
|
||||
bool m_authError = false;
|
||||
|
||||
void Clear();
|
||||
void ReportErrorAnswer(const char* msgPrefix, const char* answer);
|
||||
bool Authenticate();
|
||||
bool AuthInfoUser(int recur);
|
||||
bool AuthInfoPass(int recur);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,81 +15,28 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "ServerPool.h"
|
||||
#include "Util.h"
|
||||
|
||||
static const int CONNECTION_HOLD_SECODNS = 5;
|
||||
|
||||
ServerPool::PooledConnection::PooledConnection(NewsServer* server) : NNTPConnection(server)
|
||||
void ServerPool::PooledConnection::SetFreeTimeNow()
|
||||
{
|
||||
m_bInUse = false;
|
||||
m_tFreeTime = 0;
|
||||
m_freeTime = Util::CurrentTime();
|
||||
}
|
||||
|
||||
ServerPool::ServerPool()
|
||||
{
|
||||
debug("Creating ServerPool");
|
||||
|
||||
m_iMaxNormLevel = 0;
|
||||
m_iTimeout = 60;
|
||||
m_iGeneration = 0;
|
||||
|
||||
g_pLog->RegisterDebuggable(this);
|
||||
}
|
||||
|
||||
ServerPool::~ ServerPool()
|
||||
{
|
||||
debug("Destroying ServerPool");
|
||||
|
||||
g_pLog->UnregisterDebuggable(this);
|
||||
|
||||
m_Levels.clear();
|
||||
|
||||
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
m_Servers.clear();
|
||||
m_SortedServers.clear();
|
||||
|
||||
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
m_Connections.clear();
|
||||
}
|
||||
|
||||
void ServerPool::AddServer(NewsServer* pNewsServer)
|
||||
void ServerPool::AddServer(std::unique_ptr<NewsServer> newsServer)
|
||||
{
|
||||
debug("Adding server to ServerPool");
|
||||
|
||||
m_Servers.push_back(pNewsServer);
|
||||
m_SortedServers.push_back(pNewsServer);
|
||||
m_sortedServers.push_back(newsServer.get());
|
||||
m_servers.push_back(std::move(newsServer));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -100,224 +47,304 @@ void ServerPool::AddServer(NewsServer* pNewsServer)
|
||||
**/
|
||||
void ServerPool::NormalizeLevels()
|
||||
{
|
||||
if (m_Servers.empty())
|
||||
if (m_servers.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(m_SortedServers.begin(), m_SortedServers.end(), CompareServers);
|
||||
std::sort(m_sortedServers.begin(), m_sortedServers.end(),
|
||||
[](NewsServer* server1, NewsServer* server2)
|
||||
{
|
||||
return server1->GetLevel() < server2->GetLevel();
|
||||
});
|
||||
|
||||
// find minimum level
|
||||
int iMinLevel = m_SortedServers.front()->GetLevel();
|
||||
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
|
||||
int minLevel = m_sortedServers.front()->GetLevel();
|
||||
for (NewsServer* newsServer : m_sortedServers)
|
||||
{
|
||||
NewsServer* pNewsServer = *it;
|
||||
if (pNewsServer->GetLevel() < iMinLevel)
|
||||
if (newsServer->GetLevel() < minLevel)
|
||||
{
|
||||
iMinLevel = pNewsServer->GetLevel();
|
||||
minLevel = newsServer->GetLevel();
|
||||
}
|
||||
}
|
||||
|
||||
m_iMaxNormLevel = 0;
|
||||
int iLastLevel = iMinLevel;
|
||||
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
|
||||
m_maxNormLevel = 0;
|
||||
int lastLevel = minLevel;
|
||||
for (NewsServer* newsServer : m_sortedServers)
|
||||
{
|
||||
NewsServer* pNewsServer = *it;
|
||||
if ((pNewsServer->GetActive() && pNewsServer->GetMaxConnections() > 0) ||
|
||||
(pNewsServer->GetLevel() == iMinLevel))
|
||||
if ((newsServer->GetActive() && newsServer->GetMaxConnections() > 0) ||
|
||||
(newsServer->GetLevel() == minLevel))
|
||||
{
|
||||
if (pNewsServer->GetLevel() != iLastLevel)
|
||||
if (newsServer->GetLevel() != lastLevel)
|
||||
{
|
||||
m_iMaxNormLevel++;
|
||||
m_maxNormLevel++;
|
||||
}
|
||||
pNewsServer->SetNormLevel(m_iMaxNormLevel);
|
||||
iLastLevel = pNewsServer->GetLevel();
|
||||
newsServer->SetNormLevel(m_maxNormLevel);
|
||||
lastLevel = newsServer->GetLevel();
|
||||
}
|
||||
else
|
||||
{
|
||||
pNewsServer->SetNormLevel(-1);
|
||||
newsServer->SetNormLevel(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerPool::CompareServers(NewsServer* pServer1, NewsServer* pServer2)
|
||||
{
|
||||
return pServer1->GetLevel() < pServer2->GetLevel();
|
||||
}
|
||||
|
||||
void ServerPool::InitConnections()
|
||||
{
|
||||
debug("Initializing connections in ServerPool");
|
||||
|
||||
m_mutexConnections.Lock();
|
||||
Guard guard(m_connectionsMutex);
|
||||
|
||||
NormalizeLevels();
|
||||
m_Levels.clear();
|
||||
m_levels.clear();
|
||||
|
||||
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
|
||||
for (NewsServer* newsServer : m_sortedServers)
|
||||
{
|
||||
NewsServer* pNewsServer = *it;
|
||||
int iNormLevel = pNewsServer->GetNormLevel();
|
||||
if (pNewsServer->GetNormLevel() > -1)
|
||||
newsServer->SetBlockTime(0);
|
||||
int normLevel = newsServer->GetNormLevel();
|
||||
if (newsServer->GetNormLevel() > -1)
|
||||
{
|
||||
if ((int)m_Levels.size() <= iNormLevel)
|
||||
if ((int)m_levels.size() <= normLevel)
|
||||
{
|
||||
m_Levels.push_back(0);
|
||||
m_levels.push_back(0);
|
||||
}
|
||||
|
||||
if (pNewsServer->GetActive())
|
||||
if (newsServer->GetActive())
|
||||
{
|
||||
int iConnections = 0;
|
||||
|
||||
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
|
||||
int connections = 0;
|
||||
|
||||
for (PooledConnection* connection : &m_connections)
|
||||
{
|
||||
PooledConnection* pConnection = *it;
|
||||
if (pConnection->GetNewsServer() == pNewsServer)
|
||||
if (connection->GetNewsServer() == newsServer)
|
||||
{
|
||||
iConnections++;
|
||||
connections++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = iConnections; i < pNewsServer->GetMaxConnections(); i++)
|
||||
|
||||
for (int i = connections; i < newsServer->GetMaxConnections(); i++)
|
||||
{
|
||||
PooledConnection* pConnection = new PooledConnection(pNewsServer);
|
||||
pConnection->SetTimeout(m_iTimeout);
|
||||
m_Connections.push_back(pConnection);
|
||||
iConnections++;
|
||||
std::unique_ptr<PooledConnection> connection = std::make_unique<PooledConnection>(newsServer);
|
||||
connection->SetTimeout(m_timeout);
|
||||
m_connections.push_back(std::move(connection));
|
||||
connections++;
|
||||
}
|
||||
|
||||
m_Levels[iNormLevel] += iConnections;
|
||||
m_levels[normLevel] += connections;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_iGeneration++;
|
||||
|
||||
m_mutexConnections.Unlock();
|
||||
m_generation++;
|
||||
}
|
||||
|
||||
NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers)
|
||||
/* Returns connection from any server on a given level or nullptr if there is no free connection at the moment.
|
||||
* If all servers are blocked and all are optional a connection from the next level is returned instead.
|
||||
*/
|
||||
NntpConnection* ServerPool::GetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers)
|
||||
{
|
||||
PooledConnection* pConnection = NULL;
|
||||
Guard guard(m_connectionsMutex);
|
||||
|
||||
m_mutexConnections.Lock();
|
||||
|
||||
if (iLevel < (int)m_Levels.size() && m_Levels[iLevel] > 0)
|
||||
for (; level < (int)m_levels.size() && m_levels[level] > 0; level++)
|
||||
{
|
||||
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
|
||||
NntpConnection* connection = LockedGetConnection(level, wantServer, ignoreServers);
|
||||
if (connection)
|
||||
{
|
||||
PooledConnection* pCandidateConnection = *it;
|
||||
NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer();
|
||||
if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() &&
|
||||
pCandidateServer->GetNormLevel() == iLevel &&
|
||||
(!pWantServer || pCandidateServer == pWantServer ||
|
||||
(pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup())))
|
||||
{
|
||||
// free connection found, check if it's not from the server which should be ignored
|
||||
bool bUseConnection = true;
|
||||
if (pIgnoreServers && !pWantServer)
|
||||
{
|
||||
for (Servers::iterator it = pIgnoreServers->begin(); it != pIgnoreServers->end(); it++)
|
||||
{
|
||||
NewsServer* pIgnoreServer = *it;
|
||||
if (pIgnoreServer == pCandidateServer ||
|
||||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
|
||||
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
|
||||
{
|
||||
bUseConnection = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bUseConnection)
|
||||
{
|
||||
pConnection = pCandidateConnection;
|
||||
pConnection->SetInUse(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
if (pConnection)
|
||||
for (NewsServer* newsServer : m_sortedServers)
|
||||
{
|
||||
m_Levels[iLevel]--;
|
||||
if (newsServer->GetNormLevel() == level && newsServer->GetActive() &&
|
||||
!(newsServer->GetOptional() && IsServerBlocked(newsServer)))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_mutexConnections.Unlock();
|
||||
|
||||
return pConnection;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed)
|
||||
NntpConnection* ServerPool::LockedGetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers)
|
||||
{
|
||||
if (bUsed)
|
||||
if (level >= (int)m_levels.size() || m_levels[level] == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PooledConnection* connection = nullptr;
|
||||
std::vector<PooledConnection*> candidates;
|
||||
candidates.reserve(m_connections.size());
|
||||
|
||||
for (PooledConnection* candidateConnection : &m_connections)
|
||||
{
|
||||
NewsServer* candidateServer = candidateConnection->GetNewsServer();
|
||||
if (!candidateConnection->GetInUse() && candidateServer->GetActive() &&
|
||||
candidateServer->GetNormLevel() == level &&
|
||||
(!wantServer || candidateServer == wantServer ||
|
||||
(wantServer->GetGroup() > 0 && wantServer->GetGroup() == candidateServer->GetGroup())) &&
|
||||
(candidateConnection->GetStatus() == Connection::csConnected ||
|
||||
!IsServerBlocked(candidateServer)))
|
||||
{
|
||||
// free connection found, check if it's not from the server which should be ignored
|
||||
bool useConnection = true;
|
||||
if (ignoreServers && !wantServer)
|
||||
{
|
||||
for (NewsServer* ignoreServer : ignoreServers)
|
||||
{
|
||||
if (ignoreServer == candidateServer ||
|
||||
(ignoreServer->GetGroup() > 0 && ignoreServer->GetGroup() == candidateServer->GetGroup() &&
|
||||
ignoreServer->GetNormLevel() == candidateServer->GetNormLevel()))
|
||||
{
|
||||
useConnection = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidateServer->SetBlockTime(0);
|
||||
|
||||
if (useConnection)
|
||||
{
|
||||
candidates.push_back(candidateConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty())
|
||||
{
|
||||
// Peeking a random free connection. This is better than taking the first
|
||||
// available connection because provides better distribution across news servers,
|
||||
// especially when one of servers becomes unavailable or doesn't have requested articles.
|
||||
int randomIndex = rand() % candidates.size();
|
||||
connection = candidates[randomIndex];
|
||||
connection->SetInUse(true);
|
||||
}
|
||||
|
||||
if (connection)
|
||||
{
|
||||
m_levels[level]--;
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void ServerPool::FreeConnection(NntpConnection* connection, bool used)
|
||||
{
|
||||
if (used)
|
||||
{
|
||||
debug("Freeing used connection");
|
||||
}
|
||||
|
||||
m_mutexConnections.Lock();
|
||||
Guard guard(m_connectionsMutex);
|
||||
|
||||
((PooledConnection*)pConnection)->SetInUse(false);
|
||||
if (bUsed)
|
||||
((PooledConnection*)connection)->SetInUse(false);
|
||||
if (used)
|
||||
{
|
||||
((PooledConnection*)pConnection)->SetFreeTimeNow();
|
||||
((PooledConnection*)connection)->SetFreeTimeNow();
|
||||
}
|
||||
|
||||
if (pConnection->GetNewsServer()->GetNormLevel() > -1 && pConnection->GetNewsServer()->GetActive())
|
||||
if (connection->GetNewsServer()->GetNormLevel() > -1 && connection->GetNewsServer()->GetActive())
|
||||
{
|
||||
m_Levels[pConnection->GetNewsServer()->GetNormLevel()]++;
|
||||
m_levels[connection->GetNewsServer()->GetNormLevel()]++;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerPool::BlockServer(NewsServer* newsServer)
|
||||
{
|
||||
bool newBlock = false;
|
||||
{
|
||||
Guard guard(m_connectionsMutex);
|
||||
time_t curTime = Util::CurrentTime();
|
||||
newBlock = newsServer->GetBlockTime() != curTime;
|
||||
newsServer->SetBlockTime(curTime);
|
||||
}
|
||||
|
||||
m_mutexConnections.Unlock();
|
||||
if (newBlock && m_retryInterval > 0)
|
||||
{
|
||||
warn("Blocking %s (%s) for %i sec", newsServer->GetName(), newsServer->GetHost(), m_retryInterval);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerPool::IsServerBlocked(NewsServer* newsServer)
|
||||
{
|
||||
if (!newsServer->GetBlockTime())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t curTime = Util::CurrentTime();
|
||||
bool blocked = newsServer->GetBlockTime() <= curTime &&
|
||||
curTime < newsServer->GetBlockTime() + m_retryInterval;
|
||||
return blocked;
|
||||
}
|
||||
|
||||
void ServerPool::CloseUnusedConnections()
|
||||
{
|
||||
m_mutexConnections.Lock();
|
||||
Guard guard(m_connectionsMutex);
|
||||
|
||||
time_t curtime = ::time(NULL);
|
||||
time_t curtime = Util::CurrentTime();
|
||||
|
||||
int i = 0;
|
||||
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); )
|
||||
// close and free all connections of servers which were disabled since the last check
|
||||
m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(),
|
||||
[](std::unique_ptr<PooledConnection>& connection)
|
||||
{
|
||||
if (!connection->GetInUse() &&
|
||||
(connection->GetNewsServer()->GetNormLevel() == -1 ||
|
||||
!connection->GetNewsServer()->GetActive()))
|
||||
{
|
||||
debug("Closing (and deleting) unused connection to server%i", connection->GetNewsServer()->GetId());
|
||||
if (connection->GetStatus() == Connection::csConnected)
|
||||
{
|
||||
connection->Disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
m_connections.end());
|
||||
|
||||
// close all opened connections on levels not having any in-use connections
|
||||
for (int level = 0; level <= m_maxNormLevel; level++)
|
||||
{
|
||||
PooledConnection* pConnection = *it;
|
||||
bool bDeleted = false;
|
||||
|
||||
if (!pConnection->GetInUse() &&
|
||||
(pConnection->GetNewsServer()->GetNormLevel() == -1 ||
|
||||
!pConnection->GetNewsServer()->GetActive()))
|
||||
// check if we have in-use connections on the level
|
||||
bool hasInUseConnections = false;
|
||||
int inactiveTime = 0;
|
||||
for (PooledConnection* connection : &m_connections)
|
||||
{
|
||||
debug("Closing (and deleting) unused connection to server%i", pConnection->GetNewsServer()->GetID());
|
||||
if (pConnection->GetStatus() == Connection::csConnected)
|
||||
if (connection->GetNewsServer()->GetNormLevel() == level)
|
||||
{
|
||||
pConnection->Disconnect();
|
||||
}
|
||||
delete pConnection;
|
||||
m_Connections.erase(it);
|
||||
it = m_Connections.begin() + i;
|
||||
bDeleted = true;
|
||||
}
|
||||
|
||||
if (!bDeleted && !pConnection->GetInUse() && pConnection->GetStatus() == Connection::csConnected)
|
||||
{
|
||||
int tdiff = (int)(curtime - pConnection->GetFreeTime());
|
||||
if (tdiff > CONNECTION_HOLD_SECODNS)
|
||||
{
|
||||
debug("Closing (and keeping) unused connection to server%i", pConnection->GetNewsServer()->GetID());
|
||||
pConnection->Disconnect();
|
||||
if (connection->GetInUse())
|
||||
{
|
||||
hasInUseConnections = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
int tdiff = (int)(curtime - connection->GetFreeTime());
|
||||
if (tdiff > inactiveTime)
|
||||
{
|
||||
inactiveTime = tdiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bDeleted)
|
||||
// if there are no in-use connections on the level and the hold time out has
|
||||
// expired - close all connections of the level.
|
||||
if (!hasInUseConnections && inactiveTime > CONNECTION_HOLD_SECODNS)
|
||||
{
|
||||
it++;
|
||||
i++;
|
||||
for (PooledConnection* connection : &m_connections)
|
||||
{
|
||||
if (connection->GetNewsServer()->GetNormLevel() == level &&
|
||||
connection->GetStatus() == Connection::csConnected)
|
||||
{
|
||||
debug("Closing (and keeping) unused connection to server%i", connection->GetNewsServer()->GetId());
|
||||
connection->Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_mutexConnections.Unlock();
|
||||
}
|
||||
|
||||
void ServerPool::Changed()
|
||||
@@ -332,35 +359,35 @@ void ServerPool::LogDebugInfo()
|
||||
{
|
||||
info(" ---------- ServerPool");
|
||||
|
||||
info(" Max-Level: %i", m_iMaxNormLevel);
|
||||
info(" Max-Level: %i", m_maxNormLevel);
|
||||
|
||||
m_mutexConnections.Lock();
|
||||
Guard guard(m_connectionsMutex);
|
||||
|
||||
info(" Servers: %i", m_Servers.size());
|
||||
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
|
||||
time_t curTime = Util::CurrentTime();
|
||||
|
||||
info(" Servers: %i", (int)m_servers.size());
|
||||
for (NewsServer* newsServer : &m_servers)
|
||||
{
|
||||
NewsServer* pNewsServer = *it;
|
||||
info(" %i) %s (%s): Level=%i, NormLevel=%i", pNewsServer->GetID(), pNewsServer->GetName(),
|
||||
pNewsServer->GetHost(), pNewsServer->GetLevel(), pNewsServer->GetNormLevel());
|
||||
info(" %i) %s (%s): Level=%i, NormLevel=%i, BlockSec=%i", newsServer->GetId(), newsServer->GetName(),
|
||||
newsServer->GetHost(), newsServer->GetLevel(), newsServer->GetNormLevel(),
|
||||
newsServer->GetBlockTime() && newsServer->GetBlockTime() + m_retryInterval > curTime ?
|
||||
(int)(newsServer->GetBlockTime() + m_retryInterval - curTime) : 0);
|
||||
}
|
||||
|
||||
info(" Levels: %i", m_Levels.size());
|
||||
info(" Levels: %i", (int)m_levels.size());
|
||||
int index = 0;
|
||||
for (Levels::iterator it = m_Levels.begin(); it != m_Levels.end(); it++, index++)
|
||||
for (int size : m_levels)
|
||||
{
|
||||
int iSize = *it;
|
||||
info(" %i: Free connections=%i", index, iSize);
|
||||
info(" %i: Free connections=%i", index, size);
|
||||
index++;
|
||||
}
|
||||
|
||||
info(" Connections: %i", m_Connections.size());
|
||||
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
|
||||
info(" Connections: %i", (int)m_connections.size());
|
||||
for (PooledConnection* connection : &m_connections)
|
||||
{
|
||||
PooledConnection* pConnection = *it;
|
||||
info(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", pConnection->GetNewsServer()->GetID(),
|
||||
pConnection->GetNewsServer()->GetName(), pConnection->GetNewsServer()->GetHost(),
|
||||
pConnection->GetNewsServer()->GetLevel(), pConnection->GetNewsServer()->GetNormLevel(),
|
||||
(int)pConnection->GetInUse());
|
||||
info(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", connection->GetNewsServer()->GetId(),
|
||||
connection->GetNewsServer()->GetName(), connection->GetNewsServer()->GetHost(),
|
||||
connection->GetNewsServer()->GetLevel(), connection->GetNewsServer()->GetNormLevel(),
|
||||
(int)connection->GetInUse());
|
||||
}
|
||||
|
||||
m_mutexConnections.Unlock();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -15,73 +15,72 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SERVERPOOL_H
|
||||
#define SERVERPOOL_H
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Container.h"
|
||||
#include "Thread.h"
|
||||
#include "NewsServer.h"
|
||||
#include "NNTPConnection.h"
|
||||
#include "NntpConnection.h"
|
||||
|
||||
class ServerPool : public Debuggable
|
||||
{
|
||||
private:
|
||||
class PooledConnection : public NNTPConnection
|
||||
{
|
||||
private:
|
||||
bool m_bInUse;
|
||||
time_t m_tFreeTime;
|
||||
public:
|
||||
PooledConnection(NewsServer* server);
|
||||
bool GetInUse() { return m_bInUse; }
|
||||
void SetInUse(bool bInUse) { m_bInUse = bInUse; }
|
||||
time_t GetFreeTime() { return m_tFreeTime; }
|
||||
void SetFreeTimeNow() { m_tFreeTime = ::time(NULL); }
|
||||
};
|
||||
public:
|
||||
typedef std::vector<NewsServer*> RawServerList;
|
||||
|
||||
typedef std::vector<int> Levels;
|
||||
typedef std::vector<PooledConnection*> Connections;
|
||||
|
||||
Servers m_Servers;
|
||||
Servers m_SortedServers;
|
||||
Connections m_Connections;
|
||||
Levels m_Levels;
|
||||
int m_iMaxNormLevel;
|
||||
Mutex m_mutexConnections;
|
||||
int m_iTimeout;
|
||||
int m_iGeneration;
|
||||
|
||||
void NormalizeLevels();
|
||||
static bool CompareServers(NewsServer* pServer1, NewsServer* pServer2);
|
||||
void SetTimeout(int timeout) { m_timeout = timeout; }
|
||||
void SetRetryInterval(int retryInterval) { m_retryInterval = retryInterval; }
|
||||
void AddServer(std::unique_ptr<NewsServer> newsServer);
|
||||
void InitConnections();
|
||||
int GetMaxNormLevel() { return m_maxNormLevel; }
|
||||
Servers* GetServers() { return &m_servers; } // Only for read access (no lockings)
|
||||
NntpConnection* GetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers);
|
||||
void FreeConnection(NntpConnection* connection, bool used);
|
||||
void CloseUnusedConnections();
|
||||
void Changed();
|
||||
int GetGeneration() { return m_generation; }
|
||||
void BlockServer(NewsServer* newsServer);
|
||||
bool IsServerBlocked(NewsServer* newsServer);
|
||||
|
||||
protected:
|
||||
virtual void LogDebugInfo();
|
||||
virtual void LogDebugInfo();
|
||||
|
||||
public:
|
||||
ServerPool();
|
||||
~ServerPool();
|
||||
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
|
||||
void AddServer(NewsServer* pNewsServer);
|
||||
void InitConnections();
|
||||
int GetMaxNormLevel() { return m_iMaxNormLevel; }
|
||||
Servers* GetServers() { return &m_Servers; } // Only for read access (no lockings)
|
||||
NNTPConnection* GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers);
|
||||
void FreeConnection(NNTPConnection* pConnection, bool bUsed);
|
||||
void CloseUnusedConnections();
|
||||
void Changed();
|
||||
int GetGeneration() { return m_iGeneration; }
|
||||
private:
|
||||
class PooledConnection : public NntpConnection
|
||||
{
|
||||
public:
|
||||
using NntpConnection::NntpConnection;
|
||||
bool GetInUse() { return m_inUse; }
|
||||
void SetInUse(bool inUse) { m_inUse = inUse; }
|
||||
time_t GetFreeTime() { return m_freeTime; }
|
||||
void SetFreeTimeNow();
|
||||
private:
|
||||
bool m_inUse = false;
|
||||
time_t m_freeTime = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<int> Levels;
|
||||
typedef std::vector<std::unique_ptr<PooledConnection>> Connections;
|
||||
|
||||
Servers m_servers;
|
||||
RawServerList m_sortedServers;
|
||||
Connections m_connections;
|
||||
Levels m_levels;
|
||||
int m_maxNormLevel = 0;
|
||||
Mutex m_connectionsMutex;
|
||||
int m_timeout = 60;
|
||||
int m_retryInterval = 0;
|
||||
int m_generation = 0;
|
||||
|
||||
void NormalizeLevels();
|
||||
NntpConnection* LockedGetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers);
|
||||
};
|
||||
|
||||
extern ServerPool* g_ServerPool;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2014-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,200 +14,155 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include "win32.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "StatMeter.h"
|
||||
#include "Options.h"
|
||||
#include "WorkState.h"
|
||||
#include "ServerPool.h"
|
||||
#include "DiskState.h"
|
||||
|
||||
extern ServerPool* g_pServerPool;
|
||||
extern Options* g_pOptions;
|
||||
extern DiskState* g_pDiskState;
|
||||
#include "Util.h"
|
||||
|
||||
static const int DAYS_UP_TO_2013_JAN_1 = 15706;
|
||||
static const int DAYS_IN_TWENTY_YEARS = 366*20;
|
||||
|
||||
ServerVolume::ServerVolume()
|
||||
void ServerVolume::CalcSlots(time_t locCurTime)
|
||||
{
|
||||
m_BytesPerSeconds.resize(60);
|
||||
m_BytesPerMinutes.resize(60);
|
||||
m_BytesPerHours.resize(24);
|
||||
m_BytesPerDays.resize(0);
|
||||
m_iFirstDay = 0;
|
||||
m_tDataTime = 0;
|
||||
m_lTotalBytes = 0;
|
||||
m_lCustomBytes = 0;
|
||||
m_tCustomTime = time(NULL);
|
||||
m_iSecSlot = 0;
|
||||
m_iMinSlot = 0;
|
||||
m_iHourSlot = 0;
|
||||
m_iDaySlot = 0;
|
||||
}
|
||||
|
||||
void ServerVolume::CalcSlots(time_t tLocCurTime)
|
||||
{
|
||||
m_iSecSlot = (int)tLocCurTime % 60;
|
||||
m_iMinSlot = ((int)tLocCurTime / 60) % 60;
|
||||
m_iHourSlot = ((int)tLocCurTime % 86400) / 3600;
|
||||
int iDaysSince1970 = (int)tLocCurTime / 86400;
|
||||
m_iDaySlot = iDaysSince1970 - DAYS_UP_TO_2013_JAN_1 + 1;
|
||||
if (0 <= m_iDaySlot && m_iDaySlot < DAYS_IN_TWENTY_YEARS)
|
||||
m_secSlot = (int)locCurTime % 60;
|
||||
m_minSlot = ((int)locCurTime / 60) % 60;
|
||||
m_hourSlot = ((int)locCurTime % 86400) / 3600;
|
||||
int daysSince1970 = (int)locCurTime / 86400;
|
||||
m_daySlot = daysSince1970 - DAYS_UP_TO_2013_JAN_1 + 1;
|
||||
if (0 <= m_daySlot && m_daySlot < DAYS_IN_TWENTY_YEARS)
|
||||
{
|
||||
int iCurDay = iDaysSince1970;
|
||||
if (m_iFirstDay == 0 || m_iFirstDay > iCurDay)
|
||||
int curDay = daysSince1970;
|
||||
if (m_firstDay == 0 || m_firstDay > curDay)
|
||||
{
|
||||
m_iFirstDay = iCurDay;
|
||||
m_firstDay = curDay;
|
||||
}
|
||||
m_iDaySlot = iCurDay - m_iFirstDay;
|
||||
if (m_iDaySlot + 1 > (int)m_BytesPerDays.size())
|
||||
m_daySlot = curDay - m_firstDay;
|
||||
if (m_daySlot + 1 > (int)m_bytesPerDays.size())
|
||||
{
|
||||
m_BytesPerDays.resize(m_iDaySlot + 1);
|
||||
m_bytesPerDays.resize(m_daySlot + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iDaySlot = -1;
|
||||
m_daySlot = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerVolume::AddData(int iBytes)
|
||||
void ServerVolume::AddData(int bytes)
|
||||
{
|
||||
time_t tCurTime = time(NULL);
|
||||
time_t tLocCurTime = tCurTime + g_pOptions->GetLocalTimeOffset();
|
||||
time_t tLocDataTime = m_tDataTime + g_pOptions->GetLocalTimeOffset();
|
||||
time_t curTime = Util::CurrentTime();
|
||||
time_t locCurTime = curTime + g_WorkState->GetLocalTimeOffset();
|
||||
time_t locDataTime = m_dataTime + g_WorkState->GetLocalTimeOffset();
|
||||
|
||||
int iLastMinSlot = m_iMinSlot;
|
||||
int iLastHourSlot = m_iHourSlot;
|
||||
int lastMinSlot = m_minSlot;
|
||||
int lastHourSlot = m_hourSlot;
|
||||
|
||||
CalcSlots(tLocCurTime);
|
||||
CalcSlots(locCurTime);
|
||||
|
||||
if (tLocCurTime != tLocDataTime)
|
||||
if (locCurTime != locDataTime)
|
||||
{
|
||||
// clear seconds/minutes/hours slots if necessary
|
||||
// also handle the backwards changes of system clock
|
||||
|
||||
int iTotalDelta = (int)(tLocCurTime - tLocDataTime);
|
||||
int iDeltaSign = iTotalDelta >= 0 ? 1 : -1;
|
||||
iTotalDelta = abs(iTotalDelta);
|
||||
int totalDelta = (int)(locCurTime - locDataTime);
|
||||
int deltaSign = totalDelta >= 0 ? 1 : -1;
|
||||
totalDelta = abs(totalDelta);
|
||||
|
||||
int iSecDelta = iTotalDelta;
|
||||
if (iDeltaSign < 0) iSecDelta++;
|
||||
if (iSecDelta >= 60) iSecDelta = 60;
|
||||
for (int i = 0; i < iSecDelta; i++)
|
||||
int secDelta = totalDelta;
|
||||
if (deltaSign < 0) secDelta++;
|
||||
if (secDelta >= 60) secDelta = 60;
|
||||
for (int i = 0; i < secDelta; i++)
|
||||
{
|
||||
int iNulSlot = m_iSecSlot - i * iDeltaSign;
|
||||
if (iNulSlot < 0) iNulSlot += 60;
|
||||
if (iNulSlot >= 60) iNulSlot -= 60;
|
||||
m_BytesPerSeconds[iNulSlot] = 0;
|
||||
int nulSlot = m_secSlot - i * deltaSign;
|
||||
if (nulSlot < 0) nulSlot += 60;
|
||||
if (nulSlot >= 60) nulSlot -= 60;
|
||||
m_bytesPerSeconds[nulSlot] = 0;
|
||||
}
|
||||
|
||||
int iMinDelta = iTotalDelta / 60;
|
||||
if (iDeltaSign < 0) iMinDelta++;
|
||||
if (abs(iMinDelta) >= 60) iMinDelta = 60;
|
||||
if (iMinDelta == 0 && m_iMinSlot != iLastMinSlot) iMinDelta = 1;
|
||||
for (int i = 0; i < iMinDelta; i++)
|
||||
int minDelta = totalDelta / 60;
|
||||
if (deltaSign < 0) minDelta++;
|
||||
if (abs(minDelta) >= 60) minDelta = 60;
|
||||
if (minDelta == 0 && m_minSlot != lastMinSlot) minDelta = 1;
|
||||
for (int i = 0; i < minDelta; i++)
|
||||
{
|
||||
int iNulSlot = m_iMinSlot - i * iDeltaSign;
|
||||
if (iNulSlot < 0) iNulSlot += 60;
|
||||
if (iNulSlot >= 60) iNulSlot -= 60;
|
||||
m_BytesPerMinutes[iNulSlot] = 0;
|
||||
int nulSlot = m_minSlot - i * deltaSign;
|
||||
if (nulSlot < 0) nulSlot += 60;
|
||||
if (nulSlot >= 60) nulSlot -= 60;
|
||||
m_bytesPerMinutes[nulSlot] = 0;
|
||||
}
|
||||
|
||||
int iHourDelta = iTotalDelta / (60 * 60);
|
||||
if (iDeltaSign < 0) iHourDelta++;
|
||||
if (iHourDelta >= 24) iHourDelta = 24;
|
||||
if (iHourDelta == 0 && m_iHourSlot != iLastHourSlot) iHourDelta = 1;
|
||||
for (int i = 0; i < iHourDelta; i++)
|
||||
int hourDelta = totalDelta / (60 * 60);
|
||||
if (deltaSign < 0) hourDelta++;
|
||||
if (hourDelta >= 24) hourDelta = 24;
|
||||
if (hourDelta == 0 && m_hourSlot != lastHourSlot) hourDelta = 1;
|
||||
for (int i = 0; i < hourDelta; i++)
|
||||
{
|
||||
int iNulSlot = m_iHourSlot - i * iDeltaSign;
|
||||
if (iNulSlot < 0) iNulSlot += 24;
|
||||
if (iNulSlot >= 24) iNulSlot -= 24;
|
||||
m_BytesPerHours[iNulSlot] = 0;
|
||||
int nulSlot = m_hourSlot - i * deltaSign;
|
||||
if (nulSlot < 0) nulSlot += 24;
|
||||
if (nulSlot >= 24) nulSlot -= 24;
|
||||
m_bytesPerHours[nulSlot] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// add bytes to every slot
|
||||
m_BytesPerSeconds[m_iSecSlot] += iBytes;
|
||||
m_BytesPerMinutes[m_iMinSlot] += iBytes;
|
||||
m_BytesPerHours[m_iHourSlot] += iBytes;
|
||||
if (m_iDaySlot >= 0)
|
||||
m_bytesPerSeconds[m_secSlot] += bytes;
|
||||
m_bytesPerMinutes[m_minSlot] += bytes;
|
||||
m_bytesPerHours[m_hourSlot] += bytes;
|
||||
if (m_daySlot >= 0)
|
||||
{
|
||||
m_BytesPerDays[m_iDaySlot] += iBytes;
|
||||
m_bytesPerDays[m_daySlot] += bytes;
|
||||
}
|
||||
m_lTotalBytes += iBytes;
|
||||
m_lCustomBytes += iBytes;
|
||||
m_totalBytes += bytes;
|
||||
m_customBytes += bytes;
|
||||
|
||||
m_tDataTime = tCurTime;
|
||||
m_dataTime = curTime;
|
||||
}
|
||||
|
||||
void ServerVolume::ResetCustom()
|
||||
{
|
||||
m_lCustomBytes = 0;
|
||||
m_tCustomTime = time(NULL);
|
||||
m_customBytes = 0;
|
||||
m_customTime = Util::CurrentTime();
|
||||
}
|
||||
|
||||
void ServerVolume::LogDebugInfo()
|
||||
{
|
||||
info(" ---------- ServerVolume");
|
||||
|
||||
char szSec[4000];
|
||||
StringBuilder msg;
|
||||
|
||||
szSec[0] = '\0';
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
char szNum[20];
|
||||
snprintf(szNum, 20, "[%i]=%lli ", i, m_BytesPerSeconds[i]);
|
||||
strncat(szSec, szNum, 4000);
|
||||
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerSeconds[i]);
|
||||
}
|
||||
info("Secs: %s", szSec);
|
||||
info("Secs: %s", *msg);
|
||||
|
||||
szSec[0] = '\0';
|
||||
msg.Clear();
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
char szNum[20];
|
||||
snprintf(szNum, 20, "[%i]=%lli ", i, m_BytesPerMinutes[i]);
|
||||
strncat(szSec, szNum, 4000);
|
||||
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerMinutes[i]);
|
||||
}
|
||||
info("Mins: %s", szSec);
|
||||
info("Mins: %s", *msg);
|
||||
|
||||
szSec[0] = '\0';
|
||||
msg.Clear();
|
||||
for (int i = 0; i < 24; i++)
|
||||
{
|
||||
char szNum[20];
|
||||
snprintf(szNum, 20, "[%i]=%lli ", i, m_BytesPerHours[i]);
|
||||
strncat(szSec, szNum, 4000);
|
||||
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerHours[i]);
|
||||
}
|
||||
info("Hours: %s", szSec);
|
||||
info("Hours: %s", *msg);
|
||||
|
||||
szSec[0] = '\0';
|
||||
for (int i = 0; i < (int)m_BytesPerDays.size(); i++)
|
||||
msg.Clear();
|
||||
for (int i = 0; i < (int)m_bytesPerDays.size(); i++)
|
||||
{
|
||||
char szNum[20];
|
||||
snprintf(szNum, 20, "[%i]=%lli ", m_iFirstDay + i, m_BytesPerDays[i]);
|
||||
strncat(szSec, szNum, 4000);
|
||||
msg.AppendFmt("[%i]=%" PRIi64 " ", m_firstDay + i, m_bytesPerDays[i]);
|
||||
}
|
||||
info("Days: %s", szSec);
|
||||
info("Days: %s", *msg);
|
||||
}
|
||||
|
||||
StatMeter::StatMeter()
|
||||
@@ -215,61 +170,29 @@ StatMeter::StatMeter()
|
||||
debug("Creating StatMeter");
|
||||
|
||||
ResetSpeedStat();
|
||||
|
||||
m_iAllBytes = 0;
|
||||
m_tStartDownload = 0;
|
||||
m_tPausedFrom = 0;
|
||||
m_bStandBy = true;
|
||||
m_tStartServer = 0;
|
||||
m_tLastCheck = 0;
|
||||
m_tLastTimeOffset = 0;
|
||||
m_bStatChanged = false;
|
||||
|
||||
g_pLog->RegisterDebuggable(this);
|
||||
}
|
||||
|
||||
StatMeter::~StatMeter()
|
||||
{
|
||||
debug("Destroying StatMeter");
|
||||
// Cleanup
|
||||
|
||||
g_pLog->UnregisterDebuggable(this);
|
||||
|
||||
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
|
||||
debug("StatMeter destroyed");
|
||||
}
|
||||
|
||||
void StatMeter::Init()
|
||||
{
|
||||
m_tStartServer = time(NULL);
|
||||
m_tLastCheck = m_tStartServer;
|
||||
m_startServer = Util::CurrentTime();
|
||||
m_lastCheck = m_startServer;
|
||||
AdjustTimeOffset();
|
||||
|
||||
m_ServerVolumes.resize(1 + g_pServerPool->GetServers()->size());
|
||||
m_ServerVolumes[0] = new ServerVolume();
|
||||
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
|
||||
{
|
||||
NewsServer* pServer = *it;
|
||||
m_ServerVolumes[pServer->GetID()] = new ServerVolume();
|
||||
}
|
||||
m_serverVolumes.resize(1 + g_ServerPool->GetServers()->size());
|
||||
}
|
||||
|
||||
void StatMeter::AdjustTimeOffset()
|
||||
{
|
||||
time_t tUtcTime = time(NULL);
|
||||
time_t utcTime = Util::CurrentTime();
|
||||
tm tmSplittedTime;
|
||||
gmtime_r(&tUtcTime, &tmSplittedTime);
|
||||
gmtime_r(&utcTime, &tmSplittedTime);
|
||||
tmSplittedTime.tm_isdst = -1;
|
||||
time_t tLocTime = mktime(&tmSplittedTime);
|
||||
time_t tLocalTimeDelta = tUtcTime - tLocTime;
|
||||
g_pOptions->SetLocalTimeOffset((int)tLocalTimeDelta + g_pOptions->GetTimeCorrection());
|
||||
m_tLastTimeOffset = tUtcTime;
|
||||
time_t locTime = mktime(&tmSplittedTime);
|
||||
time_t localTimeDelta = utcTime - locTime;
|
||||
g_WorkState->SetLocalTimeOffset((int)localTimeDelta + g_Options->GetTimeCorrection());
|
||||
m_lastTimeOffset = utcTime;
|
||||
|
||||
debug("UTC delta: %i (%i+%i)", g_pOptions->GetLocalTimeOffset(), (int)tLocalTimeDelta, g_pOptions->GetTimeCorrection());
|
||||
debug("UTC delta: %i (%i+%i)", g_WorkState->GetLocalTimeOffset(), (int)localTimeDelta, g_Options->GetTimeCorrection());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -279,20 +202,20 @@ void StatMeter::AdjustTimeOffset()
|
||||
*/
|
||||
void StatMeter::IntervalCheck()
|
||||
{
|
||||
time_t m_tCurTime = time(NULL);
|
||||
time_t tDiff = m_tCurTime - m_tLastCheck;
|
||||
if (tDiff > 60 || tDiff < 0)
|
||||
time_t m_curTime = Util::CurrentTime();
|
||||
time_t diff = m_curTime - m_lastCheck;
|
||||
if (diff > 60 || diff < 0)
|
||||
{
|
||||
m_tStartServer += tDiff + 1; // "1" because the method is called once per second
|
||||
if (m_tStartDownload != 0 && !m_bStandBy)
|
||||
m_startServer += diff + 1; // "1" because the method is called once per second
|
||||
if (m_startDownload != 0 && !m_standBy)
|
||||
{
|
||||
m_tStartDownload += tDiff + 1;
|
||||
m_startDownload += diff + 1;
|
||||
}
|
||||
AdjustTimeOffset();
|
||||
}
|
||||
else if (m_tLastTimeOffset > m_tCurTime ||
|
||||
m_tCurTime - m_tLastTimeOffset > 60 * 60 * 3 ||
|
||||
(m_tCurTime - m_tLastTimeOffset > 60 && !m_bStandBy))
|
||||
else if (m_lastTimeOffset > m_curTime ||
|
||||
m_curTime - m_lastTimeOffset > 60 * 60 * 3 ||
|
||||
(m_curTime - m_lastTimeOffset > 60 && !m_standBy))
|
||||
{
|
||||
// checking time zone settings may prevent the device from entering sleep/hibernate mode
|
||||
// check every minute if not in standby
|
||||
@@ -300,247 +223,322 @@ void StatMeter::IntervalCheck()
|
||||
AdjustTimeOffset();
|
||||
}
|
||||
|
||||
m_tLastCheck = m_tCurTime;
|
||||
m_lastCheck = m_curTime;
|
||||
|
||||
if (m_bStatChanged)
|
||||
CheckQuota();
|
||||
|
||||
if (m_statChanged)
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
void StatMeter::EnterLeaveStandBy(bool bEnter)
|
||||
void StatMeter::EnterLeaveStandBy(bool enter)
|
||||
{
|
||||
m_mutexStat.Lock();
|
||||
m_bStandBy = bEnter;
|
||||
if (bEnter)
|
||||
Guard guard(m_statMutex);
|
||||
m_standBy = enter;
|
||||
if (enter)
|
||||
{
|
||||
m_tPausedFrom = time(NULL);
|
||||
m_pausedFrom = Util::CurrentTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_tStartDownload == 0)
|
||||
if (m_startDownload == 0)
|
||||
{
|
||||
m_tStartDownload = time(NULL);
|
||||
m_startDownload = Util::CurrentTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tStartDownload += time(NULL) - m_tPausedFrom;
|
||||
m_startDownload += Util::CurrentTime() - m_pausedFrom;
|
||||
}
|
||||
m_tPausedFrom = 0;
|
||||
m_pausedFrom = 0;
|
||||
ResetSpeedStat();
|
||||
}
|
||||
m_mutexStat.Unlock();
|
||||
}
|
||||
|
||||
void StatMeter::CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy)
|
||||
void StatMeter::CalcTotalStat(int* upTimeSec, int* dnTimeSec, int64* allBytes, bool* standBy)
|
||||
{
|
||||
m_mutexStat.Lock();
|
||||
if (m_tStartServer > 0)
|
||||
Guard guard(m_statMutex);
|
||||
if (m_startServer > 0)
|
||||
{
|
||||
*iUpTimeSec = (int)(time(NULL) - m_tStartServer);
|
||||
*upTimeSec = (int)(Util::CurrentTime() - m_startServer);
|
||||
}
|
||||
else
|
||||
{
|
||||
*iUpTimeSec = 0;
|
||||
*upTimeSec = 0;
|
||||
}
|
||||
*bStandBy = m_bStandBy;
|
||||
if (m_bStandBy)
|
||||
*standBy = m_standBy;
|
||||
if (m_standBy)
|
||||
{
|
||||
*iDnTimeSec = (int)(m_tPausedFrom - m_tStartDownload);
|
||||
*dnTimeSec = (int)(m_pausedFrom - m_startDownload);
|
||||
}
|
||||
else
|
||||
{
|
||||
*iDnTimeSec = (int)(time(NULL) - m_tStartDownload);
|
||||
*dnTimeSec = (int)(Util::CurrentTime() - m_startDownload);
|
||||
}
|
||||
*iAllBytes = m_iAllBytes;
|
||||
m_mutexStat.Unlock();
|
||||
*allBytes = m_allBytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: see note to "AddSpeedReading"
|
||||
*/
|
||||
// Average speed in last 30 seconds
|
||||
int StatMeter::CalcCurrentDownloadSpeed()
|
||||
{
|
||||
if (m_bStandBy)
|
||||
if (m_standBy)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE;
|
||||
if (iTimeDiff == 0)
|
||||
int timeDiff = (int)Util::CurrentTime() - m_speedStartTime * SPEEDMETER_SLOTSIZE;
|
||||
if (timeDiff == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)(m_iSpeedTotalBytes / iTimeDiff);
|
||||
return (int)(m_speedTotalBytes / timeDiff);
|
||||
}
|
||||
|
||||
void StatMeter::AddSpeedReading(int iBytes)
|
||||
// Amount of data downloaded in current second
|
||||
int StatMeter::CalcMomentaryDownloadSpeed()
|
||||
{
|
||||
time_t tCurTime = time(NULL);
|
||||
int iNowSlot = (int)tCurTime / SPEEDMETER_SLOTSIZE;
|
||||
time_t curTime = Util::CurrentTime();
|
||||
int speed = curTime == m_curSecTime ? m_curSecBytes : 0;
|
||||
return speed;
|
||||
}
|
||||
|
||||
if (g_pOptions->GetAccurateRate())
|
||||
void StatMeter::AddSpeedReading(int bytes)
|
||||
{
|
||||
time_t curTime = Util::CurrentTime();
|
||||
int nowSlot = (int)curTime / SPEEDMETER_SLOTSIZE;
|
||||
|
||||
if (curTime != m_curSecTime)
|
||||
{
|
||||
#ifdef HAVE_SPINLOCK
|
||||
m_spinlockSpeed.Lock();
|
||||
#else
|
||||
m_mutexSpeed.Lock();
|
||||
#endif
|
||||
m_curSecTime = curTime;
|
||||
m_curSecBytes = 0;
|
||||
}
|
||||
m_curSecBytes += bytes;
|
||||
|
||||
while (iNowSlot > m_iSpeedTime[m_iSpeedBytesIndex])
|
||||
while (nowSlot > m_speedTime[m_speedBytesIndex])
|
||||
{
|
||||
//record bytes in next slot
|
||||
m_iSpeedBytesIndex++;
|
||||
if (m_iSpeedBytesIndex >= SPEEDMETER_SLOTS)
|
||||
m_speedBytesIndex++;
|
||||
if (m_speedBytesIndex >= SPEEDMETER_SLOTS)
|
||||
{
|
||||
m_iSpeedBytesIndex = 0;
|
||||
m_speedBytesIndex = 0;
|
||||
}
|
||||
//Adjust counters with outgoing information.
|
||||
m_iSpeedTotalBytes = m_iSpeedTotalBytes - (long long)m_iSpeedBytes[m_iSpeedBytesIndex];
|
||||
m_speedTotalBytes = m_speedTotalBytes - (int64)m_speedBytes[m_speedBytesIndex];
|
||||
|
||||
//Note we should really use the start time of the next slot
|
||||
//but its easier to just use the outgoing slot time. This
|
||||
//will result in a small error.
|
||||
m_iSpeedStartTime = m_iSpeedTime[m_iSpeedBytesIndex];
|
||||
m_speedStartTime = m_speedTime[m_speedBytesIndex];
|
||||
|
||||
//Now reset.
|
||||
m_iSpeedBytes[m_iSpeedBytesIndex] = 0;
|
||||
m_iSpeedTime[m_iSpeedBytesIndex] = iNowSlot;
|
||||
m_speedBytes[m_speedBytesIndex] = 0;
|
||||
m_speedTime[m_speedBytesIndex] = nowSlot;
|
||||
}
|
||||
|
||||
// Once per second recalculate summary field "m_iSpeedTotalBytes" to recover from possible synchronisation errors
|
||||
if (tCurTime > m_tSpeedCorrection)
|
||||
if (curTime > m_speedCorrection)
|
||||
{
|
||||
long long iSpeedTotalBytes = 0;
|
||||
int64 speedTotalBytes = 0;
|
||||
for (int i = 0; i < SPEEDMETER_SLOTS; i++)
|
||||
{
|
||||
iSpeedTotalBytes += m_iSpeedBytes[i];
|
||||
speedTotalBytes += m_speedBytes[i];
|
||||
}
|
||||
m_iSpeedTotalBytes = iSpeedTotalBytes;
|
||||
m_tSpeedCorrection = tCurTime;
|
||||
m_speedTotalBytes = speedTotalBytes;
|
||||
m_speedCorrection = curTime;
|
||||
}
|
||||
|
||||
if (m_iSpeedTotalBytes == 0)
|
||||
if (m_speedTotalBytes == 0)
|
||||
{
|
||||
m_iSpeedStartTime = iNowSlot;
|
||||
}
|
||||
m_iSpeedBytes[m_iSpeedBytesIndex] += iBytes;
|
||||
m_iSpeedTotalBytes += iBytes;
|
||||
m_iAllBytes += iBytes;
|
||||
|
||||
if (g_pOptions->GetAccurateRate())
|
||||
{
|
||||
#ifdef HAVE_SPINLOCK
|
||||
m_spinlockSpeed.Unlock();
|
||||
#else
|
||||
m_mutexSpeed.Unlock();
|
||||
#endif
|
||||
m_speedStartTime = nowSlot;
|
||||
}
|
||||
m_speedBytes[m_speedBytesIndex] += bytes;
|
||||
m_speedTotalBytes += bytes;
|
||||
m_allBytes += bytes;
|
||||
}
|
||||
|
||||
void StatMeter::ResetSpeedStat()
|
||||
{
|
||||
time_t tCurTime = time(NULL);
|
||||
m_iSpeedStartTime = (int)tCurTime / SPEEDMETER_SLOTSIZE;
|
||||
time_t curTime = Util::CurrentTime();
|
||||
m_speedStartTime = (int)curTime / SPEEDMETER_SLOTSIZE;
|
||||
for (int i = 0; i < SPEEDMETER_SLOTS; i++)
|
||||
{
|
||||
m_iSpeedBytes[i] = 0;
|
||||
m_iSpeedTime[i] = m_iSpeedStartTime;
|
||||
m_speedBytes[i] = 0;
|
||||
m_speedTime[i] = m_speedStartTime;
|
||||
}
|
||||
m_iSpeedBytesIndex = 0;
|
||||
m_iSpeedTotalBytes = 0;
|
||||
m_tSpeedCorrection = tCurTime;
|
||||
m_speedBytesIndex = 0;
|
||||
m_speedTotalBytes = 0;
|
||||
m_speedCorrection = curTime;
|
||||
m_curSecTime = 0;
|
||||
m_curSecBytes = 0;
|
||||
}
|
||||
|
||||
void StatMeter::LogDebugInfo()
|
||||
{
|
||||
info(" ---------- SpeedMeter");
|
||||
float fSpeed = (float)(CalcCurrentDownloadSpeed() / 1024.0);
|
||||
int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE;
|
||||
info(" Speed: %f", fSpeed);
|
||||
info(" SpeedStartTime: %i", m_iSpeedStartTime);
|
||||
info(" SpeedTotalBytes: %i", m_iSpeedTotalBytes);
|
||||
info(" SpeedBytesIndex: %i", m_iSpeedBytesIndex);
|
||||
info(" AllBytes: %i", m_iAllBytes);
|
||||
info(" Time: %i", (int)time(NULL));
|
||||
info(" TimeDiff: %i", iTimeDiff);
|
||||
int speed = CalcCurrentDownloadSpeed() / 1024;
|
||||
int timeDiff = (int)Util::CurrentTime() - m_speedStartTime * SPEEDMETER_SLOTSIZE;
|
||||
info(" Speed: %i", speed);
|
||||
info(" SpeedStartTime: %i", m_speedStartTime);
|
||||
info(" SpeedTotalBytes: %" PRIi64, m_speedTotalBytes);
|
||||
info(" SpeedBytesIndex: %i", m_speedBytesIndex);
|
||||
info(" AllBytes: %" PRIi64, m_allBytes);
|
||||
info(" Time: %i", (int)Util::CurrentTime());
|
||||
info(" TimeDiff: %i", timeDiff);
|
||||
for (int i=0; i < SPEEDMETER_SLOTS; i++)
|
||||
{
|
||||
info(" Bytes[%i]: %i, Time[%i]: %i", i, m_iSpeedBytes[i], i, m_iSpeedTime[i]);
|
||||
info(" Bytes[%i]: %i, Time[%i]: %i", i, m_speedBytes[i], i, m_speedTime[i]);
|
||||
}
|
||||
|
||||
m_mutexVolume.Lock();
|
||||
Guard guard(m_volumeMutex);
|
||||
int index = 0;
|
||||
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++, index++)
|
||||
for (ServerVolume& serverVolume : m_serverVolumes)
|
||||
{
|
||||
ServerVolume* pServerVolume = *it;
|
||||
info(" ServerVolume %i", index);
|
||||
pServerVolume->LogDebugInfo();
|
||||
serverVolume.LogDebugInfo();
|
||||
index++;
|
||||
}
|
||||
m_mutexVolume.Unlock();
|
||||
}
|
||||
|
||||
void StatMeter::AddServerData(int iBytes, int iServerID)
|
||||
void StatMeter::AddServerData(int bytes, int serverId)
|
||||
{
|
||||
if (iBytes == 0)
|
||||
if (bytes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_mutexVolume.Lock();
|
||||
m_ServerVolumes[0]->AddData(iBytes);
|
||||
m_ServerVolumes[iServerID]->AddData(iBytes);
|
||||
m_bStatChanged = true;
|
||||
m_mutexVolume.Unlock();
|
||||
Guard guard(m_volumeMutex);
|
||||
m_serverVolumes[0].AddData(bytes);
|
||||
m_serverVolumes[serverId].AddData(bytes);
|
||||
m_statChanged = true;
|
||||
}
|
||||
|
||||
ServerVolumes* StatMeter::LockServerVolumes()
|
||||
GuardedServerVolumes StatMeter::GuardServerVolumes()
|
||||
{
|
||||
m_mutexVolume.Lock();
|
||||
GuardedServerVolumes serverVolumes(&m_serverVolumes, &m_volumeMutex);
|
||||
|
||||
// update slots
|
||||
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
|
||||
for (ServerVolume& serverVolume : m_serverVolumes)
|
||||
{
|
||||
ServerVolume* pServerVolume = *it;
|
||||
pServerVolume->AddData(0);
|
||||
serverVolume.AddData(0);
|
||||
}
|
||||
|
||||
return &m_ServerVolumes;
|
||||
}
|
||||
|
||||
void StatMeter::UnlockServerVolumes()
|
||||
{
|
||||
m_mutexVolume.Unlock();
|
||||
return serverVolumes;
|
||||
}
|
||||
|
||||
void StatMeter::Save()
|
||||
{
|
||||
if (!g_pOptions->GetServerMode())
|
||||
if (!g_Options->GetServerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_mutexVolume.Lock();
|
||||
g_pDiskState->SaveStats(g_pServerPool->GetServers(), &m_ServerVolumes);
|
||||
m_bStatChanged = false;
|
||||
m_mutexVolume.Unlock();
|
||||
Guard guard(m_volumeMutex);
|
||||
g_DiskState->SaveStats(g_ServerPool->GetServers(), &m_serverVolumes);
|
||||
m_statChanged = false;
|
||||
}
|
||||
|
||||
bool StatMeter::Load(bool* pPerfectServerMatch)
|
||||
bool StatMeter::Load(bool* perfectServerMatch)
|
||||
{
|
||||
m_mutexVolume.Lock();
|
||||
Guard guard(m_volumeMutex);
|
||||
|
||||
bool bOK = g_pDiskState->LoadStats(g_pServerPool->GetServers(), &m_ServerVolumes, pPerfectServerMatch);
|
||||
bool ok = g_DiskState->LoadStats(g_ServerPool->GetServers(), &m_serverVolumes, perfectServerMatch);
|
||||
|
||||
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
|
||||
for (ServerVolume& serverVolume : m_serverVolumes)
|
||||
{
|
||||
ServerVolume* pServerVolume = *it;
|
||||
pServerVolume->CalcSlots(pServerVolume->GetDataTime() + g_pOptions->GetLocalTimeOffset());
|
||||
serverVolume.CalcSlots(serverVolume.GetDataTime() + g_WorkState->GetLocalTimeOffset());
|
||||
}
|
||||
|
||||
m_mutexVolume.Unlock();
|
||||
|
||||
return bOK;
|
||||
return ok;
|
||||
}
|
||||
|
||||
void StatMeter::CheckQuota()
|
||||
{
|
||||
if ((g_Options->GetDailyQuota() == 0 && g_Options->GetMonthlyQuota() == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int64 monthBytes, dayBytes;
|
||||
CalcQuotaUsage(monthBytes, dayBytes);
|
||||
|
||||
bool monthlyQuotaReached = g_Options->GetMonthlyQuota() > 0 && monthBytes >= (int64)g_Options->GetMonthlyQuota() * 1024 * 1024;
|
||||
bool dailyQuotaReached = g_Options->GetDailyQuota() > 0 && dayBytes >= (int64)g_Options->GetDailyQuota() * 1024 * 1024;
|
||||
|
||||
if (monthlyQuotaReached && !g_WorkState->GetQuotaReached())
|
||||
{
|
||||
warn("Monthly quota reached at %s", *Util::FormatSize(monthBytes));
|
||||
}
|
||||
else if (dailyQuotaReached && !g_WorkState->GetQuotaReached())
|
||||
{
|
||||
warn("Daily quota reached at %s", *Util::FormatSize(dayBytes));
|
||||
}
|
||||
else if (!monthlyQuotaReached && !dailyQuotaReached && g_WorkState->GetQuotaReached())
|
||||
{
|
||||
info("Quota lifted");
|
||||
}
|
||||
|
||||
g_WorkState->SetQuotaReached(monthlyQuotaReached || dailyQuotaReached);
|
||||
}
|
||||
|
||||
void StatMeter::CalcQuotaUsage(int64& monthBytes, int64& dayBytes)
|
||||
{
|
||||
Guard guard(m_volumeMutex);
|
||||
|
||||
ServerVolume totalVolume = m_serverVolumes[0];
|
||||
|
||||
time_t locTime = Util::CurrentTime() + g_WorkState->GetLocalTimeOffset();
|
||||
int daySlot = (int)(locTime / 86400) - totalVolume.GetFirstDay();
|
||||
|
||||
dayBytes = 0;
|
||||
if (daySlot < (int)totalVolume.BytesPerDays()->size())
|
||||
{
|
||||
dayBytes = totalVolume.BytesPerDays()->at(daySlot);
|
||||
}
|
||||
|
||||
int elapsedSlots = CalcMonthSlots(totalVolume);
|
||||
monthBytes = 0;
|
||||
int endSlot = std::max(daySlot - elapsedSlots, -1);
|
||||
for (int slot = daySlot; slot >= 0 && slot > endSlot; slot--)
|
||||
{
|
||||
if (slot < (int)totalVolume.BytesPerDays()->size())
|
||||
{
|
||||
monthBytes += totalVolume.BytesPerDays()->at(slot);
|
||||
debug("adding slot %i: %i", slot, (int)(totalVolume.BytesPerDays()->at(slot) / 1024 / 1024));
|
||||
}
|
||||
}
|
||||
|
||||
debug("month volume: %i MB", (int)(monthBytes / 1024 / 1024));
|
||||
}
|
||||
|
||||
int StatMeter::CalcMonthSlots(ServerVolume& volume)
|
||||
{
|
||||
int elapsedDays;
|
||||
|
||||
time_t locCurTime = Util::CurrentTime() + g_WorkState->GetLocalTimeOffset();
|
||||
tm dayparts;
|
||||
gmtime_r(&locCurTime, &dayparts);
|
||||
|
||||
if (g_Options->GetQuotaStartDay() > dayparts.tm_mday)
|
||||
{
|
||||
dayparts.tm_mon--;
|
||||
dayparts.tm_mday = g_Options->GetQuotaStartDay();
|
||||
time_t prevMonth = Util::Timegm(&dayparts);
|
||||
tm prevparts;
|
||||
gmtime_r(&prevMonth, &prevparts);
|
||||
if (prevparts.tm_mday != g_Options->GetQuotaStartDay())
|
||||
{
|
||||
dayparts.tm_mday = 1;
|
||||
dayparts.tm_mon++;
|
||||
prevMonth = Util::Timegm(&dayparts);
|
||||
}
|
||||
elapsedDays = (int)(locCurTime - prevMonth) / 60 / 60 / 24 + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
elapsedDays = dayparts.tm_mday - g_Options->GetQuotaStartDay() + 1;
|
||||
}
|
||||
|
||||
return elapsedDays;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
* Copyright (C) 2014-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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
|
||||
@@ -14,127 +14,119 @@
|
||||
* 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.
|
||||
*
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef STATMETER_H
|
||||
#define STATMETER_H
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Thread.h"
|
||||
#include "Util.h"
|
||||
|
||||
class ServerVolume
|
||||
{
|
||||
public:
|
||||
typedef std::vector<long long> VolumeArray;
|
||||
typedef std::vector<int64> VolumeArray;
|
||||
|
||||
VolumeArray* BytesPerSeconds() { return &m_bytesPerSeconds; }
|
||||
VolumeArray* BytesPerMinutes() { return &m_bytesPerMinutes; }
|
||||
VolumeArray* BytesPerHours() { return &m_bytesPerHours; }
|
||||
VolumeArray* BytesPerDays() { return &m_bytesPerDays; }
|
||||
void SetFirstDay(int firstDay) { m_firstDay = firstDay; }
|
||||
int GetFirstDay() { return m_firstDay; }
|
||||
void SetTotalBytes(int64 totalBytes) { m_totalBytes = totalBytes; }
|
||||
int64 GetTotalBytes() { return m_totalBytes; }
|
||||
void SetCustomBytes(int64 customBytes) { m_customBytes = customBytes; }
|
||||
int64 GetCustomBytes() { return m_customBytes; }
|
||||
int GetSecSlot() { return m_secSlot; }
|
||||
int GetMinSlot() { return m_minSlot; }
|
||||
int GetHourSlot() { return m_hourSlot; }
|
||||
int GetDaySlot() { return m_daySlot; }
|
||||
time_t GetDataTime() { return m_dataTime; }
|
||||
void SetDataTime(time_t dataTime) { m_dataTime = dataTime; }
|
||||
time_t GetCustomTime() { return m_customTime; }
|
||||
void SetCustomTime(time_t customTime) { m_customTime = customTime; }
|
||||
|
||||
void AddData(int bytes);
|
||||
void CalcSlots(time_t locCurTime);
|
||||
void ResetCustom();
|
||||
void LogDebugInfo();
|
||||
|
||||
private:
|
||||
VolumeArray m_BytesPerSeconds;
|
||||
VolumeArray m_BytesPerMinutes;
|
||||
VolumeArray m_BytesPerHours;
|
||||
VolumeArray m_BytesPerDays;
|
||||
int m_iFirstDay;
|
||||
long long m_lTotalBytes;
|
||||
long long m_lCustomBytes;
|
||||
time_t m_tDataTime;
|
||||
time_t m_tCustomTime;
|
||||
int m_iSecSlot;
|
||||
int m_iMinSlot;
|
||||
int m_iHourSlot;
|
||||
int m_iDaySlot;
|
||||
|
||||
public:
|
||||
ServerVolume();
|
||||
VolumeArray* BytesPerSeconds() { return &m_BytesPerSeconds; }
|
||||
VolumeArray* BytesPerMinutes() { return &m_BytesPerMinutes; }
|
||||
VolumeArray* BytesPerHours() { return &m_BytesPerHours; }
|
||||
VolumeArray* BytesPerDays() { return &m_BytesPerDays; }
|
||||
void SetFirstDay(int iFirstDay) { m_iFirstDay = iFirstDay; }
|
||||
int GetFirstDay() { return m_iFirstDay; }
|
||||
void SetTotalBytes(long long lTotalBytes) { m_lTotalBytes = lTotalBytes; }
|
||||
long long GetTotalBytes() { return m_lTotalBytes; }
|
||||
void SetCustomBytes(long long lCustomBytes) { m_lCustomBytes = lCustomBytes; }
|
||||
long long GetCustomBytes() { return m_lCustomBytes; }
|
||||
int GetSecSlot() { return m_iSecSlot; }
|
||||
int GetMinSlot() { return m_iMinSlot; }
|
||||
int GetHourSlot() { return m_iHourSlot; }
|
||||
int GetDaySlot() { return m_iDaySlot; }
|
||||
time_t GetDataTime() { return m_tDataTime; }
|
||||
void SetDataTime(time_t tDataTime) { m_tDataTime = tDataTime; }
|
||||
time_t GetCustomTime() { return m_tCustomTime; }
|
||||
void SetCustomTime(time_t tCustomTime) { m_tCustomTime = tCustomTime; }
|
||||
|
||||
void AddData(int iBytes);
|
||||
void CalcSlots(time_t tLocCurTime);
|
||||
void ResetCustom();
|
||||
void LogDebugInfo();
|
||||
VolumeArray m_bytesPerSeconds = VolumeArray(60);
|
||||
VolumeArray m_bytesPerMinutes = VolumeArray(60);
|
||||
VolumeArray m_bytesPerHours = VolumeArray(24);
|
||||
VolumeArray m_bytesPerDays;
|
||||
int m_firstDay = 0;
|
||||
int64 m_totalBytes = 0;
|
||||
int64 m_customBytes = 0;
|
||||
time_t m_dataTime = 0;
|
||||
time_t m_customTime = Util::CurrentTime();
|
||||
int m_secSlot = 0;
|
||||
int m_minSlot = 0;
|
||||
int m_hourSlot = 0;
|
||||
int m_daySlot = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<ServerVolume*> ServerVolumes;
|
||||
typedef std::vector<ServerVolume> ServerVolumes;
|
||||
typedef GuardedPtr<ServerVolumes> GuardedServerVolumes;
|
||||
|
||||
class StatMeter : public Debuggable
|
||||
{
|
||||
private:
|
||||
// speed meter
|
||||
static const int SPEEDMETER_SLOTS = 30;
|
||||
static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs.
|
||||
int m_iSpeedBytes[SPEEDMETER_SLOTS];
|
||||
long long m_iSpeedTotalBytes;
|
||||
int m_iSpeedTime[SPEEDMETER_SLOTS];
|
||||
int m_iSpeedStartTime;
|
||||
time_t m_tSpeedCorrection;
|
||||
int m_iSpeedBytesIndex;
|
||||
#ifdef HAVE_SPINLOCK
|
||||
SpinLock m_spinlockSpeed;
|
||||
#else
|
||||
Mutex m_mutexSpeed;
|
||||
#endif
|
||||
|
||||
// time
|
||||
long long m_iAllBytes;
|
||||
time_t m_tStartServer;
|
||||
time_t m_tLastCheck;
|
||||
time_t m_tLastTimeOffset;
|
||||
time_t m_tStartDownload;
|
||||
time_t m_tPausedFrom;
|
||||
bool m_bStandBy;
|
||||
Mutex m_mutexStat;
|
||||
|
||||
// data volume
|
||||
bool m_bStatChanged;
|
||||
ServerVolumes m_ServerVolumes;
|
||||
Mutex m_mutexVolume;
|
||||
|
||||
void ResetSpeedStat();
|
||||
void AdjustTimeOffset();
|
||||
public:
|
||||
StatMeter();
|
||||
void Init();
|
||||
int CalcCurrentDownloadSpeed();
|
||||
int CalcMomentaryDownloadSpeed();
|
||||
void AddSpeedReading(int bytes);
|
||||
void AddServerData(int bytes, int serverId);
|
||||
void CalcTotalStat(int* upTimeSec, int* dnTimeSec, int64* allBytes, bool* standBy);
|
||||
void CalcQuotaUsage(int64& monthBytes, int64& dayBytes);
|
||||
void IntervalCheck();
|
||||
void EnterLeaveStandBy(bool enter);
|
||||
GuardedServerVolumes GuardServerVolumes();
|
||||
void Save();
|
||||
bool Load(bool* perfectServerMatch);
|
||||
|
||||
protected:
|
||||
virtual void LogDebugInfo();
|
||||
virtual void LogDebugInfo();
|
||||
|
||||
public:
|
||||
StatMeter();
|
||||
~StatMeter();
|
||||
void Init();
|
||||
int CalcCurrentDownloadSpeed();
|
||||
void AddSpeedReading(int iBytes);
|
||||
void AddServerData(int iBytes, int iServerID);
|
||||
void CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy);
|
||||
bool GetStandBy() { return m_bStandBy; }
|
||||
void IntervalCheck();
|
||||
void EnterLeaveStandBy(bool bEnter);
|
||||
ServerVolumes* LockServerVolumes();
|
||||
void UnlockServerVolumes();
|
||||
void Save();
|
||||
bool Load(bool* pPerfectServerMatch);
|
||||
private:
|
||||
// speed meter
|
||||
static const int SPEEDMETER_SLOTS = 30;
|
||||
static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs.
|
||||
int m_speedBytes[SPEEDMETER_SLOTS];
|
||||
int64 m_speedTotalBytes;
|
||||
int m_speedTime[SPEEDMETER_SLOTS];
|
||||
int m_speedStartTime;
|
||||
time_t m_speedCorrection;
|
||||
int m_speedBytesIndex;
|
||||
int m_curSecBytes;
|
||||
time_t m_curSecTime;
|
||||
|
||||
// time
|
||||
int64 m_allBytes = 0;
|
||||
time_t m_startServer = 0;
|
||||
time_t m_lastCheck = 0;
|
||||
time_t m_lastTimeOffset = 0;
|
||||
time_t m_startDownload = 0;
|
||||
time_t m_pausedFrom = 0;
|
||||
bool m_standBy = true;
|
||||
Mutex m_statMutex;
|
||||
|
||||
// data volume
|
||||
bool m_statChanged = false;
|
||||
ServerVolumes m_serverVolumes;
|
||||
Mutex m_volumeMutex;
|
||||
|
||||
void ResetSpeedStat();
|
||||
void AdjustTimeOffset();
|
||||
void CheckQuota();
|
||||
int CalcMonthSlots(ServerVolume& volume);
|
||||
};
|
||||
|
||||
extern StatMeter* g_StatMeter;
|
||||
|
||||
#endif
|
||||
|
||||
146
daemon/nserv/NServFrontend.cpp
Normal file
146
daemon/nserv/NServFrontend.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NServFrontend.h"
|
||||
#include "Util.h"
|
||||
|
||||
NServFrontend::NServFrontend()
|
||||
{
|
||||
#ifdef WIN32
|
||||
m_console = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void NServFrontend::Run()
|
||||
{
|
||||
while (!IsStopped())
|
||||
{
|
||||
Update();
|
||||
Util::Sleep(100);
|
||||
}
|
||||
// Printing the last messages
|
||||
Update();
|
||||
}
|
||||
|
||||
void NServFrontend::Update()
|
||||
{
|
||||
BeforePrint();
|
||||
|
||||
{
|
||||
GuardedMessageList messages = g_Log->GuardMessages();
|
||||
if (!messages->empty())
|
||||
{
|
||||
Message& firstMessage = messages->front();
|
||||
int start = m_neededLogFirstId - firstMessage.GetId() + 1;
|
||||
if (start < 0)
|
||||
{
|
||||
PrintSkip();
|
||||
start = 0;
|
||||
}
|
||||
for (uint32 i = (uint32)start; i < messages->size(); i++)
|
||||
{
|
||||
PrintMessage(messages->at(i));
|
||||
m_neededLogFirstId = messages->at(i).GetId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void NServFrontend::BeforePrint()
|
||||
{
|
||||
if (m_needGoBack)
|
||||
{
|
||||
// go back one line
|
||||
#ifdef WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
|
||||
GetConsoleScreenBufferInfo(m_console, &BufInfo);
|
||||
BufInfo.dwCursorPosition.Y--;
|
||||
SetConsoleCursorPosition(m_console, BufInfo.dwCursorPosition);
|
||||
#else
|
||||
printf("\r\033[1A");
|
||||
#endif
|
||||
m_needGoBack = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NServFrontend::PrintMessage(Message& message)
|
||||
{
|
||||
#ifdef WIN32
|
||||
switch (message.GetKind())
|
||||
{
|
||||
case Message::mkDebug:
|
||||
SetConsoleTextAttribute(m_console, 8);
|
||||
printf("[DEBUG] ");
|
||||
break;
|
||||
case Message::mkError:
|
||||
SetConsoleTextAttribute(m_console, 4);
|
||||
printf("[ERROR] ");
|
||||
break;
|
||||
case Message::mkWarning:
|
||||
SetConsoleTextAttribute(m_console, 5);
|
||||
printf("[WARNING]");
|
||||
break;
|
||||
case Message::mkInfo:
|
||||
SetConsoleTextAttribute(m_console, 2);
|
||||
printf("[INFO] ");
|
||||
break;
|
||||
case Message::mkDetail:
|
||||
SetConsoleTextAttribute(m_console, 2);
|
||||
printf("[DETAIL]");
|
||||
break;
|
||||
}
|
||||
SetConsoleTextAttribute(m_console, 7);
|
||||
CString msg = message.GetText();
|
||||
CharToOem(msg, msg);
|
||||
printf(" %s\n", *msg);
|
||||
#else
|
||||
const char* msg = message.GetText();
|
||||
switch (message.GetKind())
|
||||
{
|
||||
case Message::mkDebug:
|
||||
printf("[DEBUG] %s\033[K\n", msg);
|
||||
break;
|
||||
case Message::mkError:
|
||||
printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg);
|
||||
break;
|
||||
case Message::mkWarning:
|
||||
printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg);
|
||||
break;
|
||||
case Message::mkInfo:
|
||||
printf("\033[32m[INFO]\033[39m %s\033[K\n", msg);
|
||||
break;
|
||||
case Message::mkDetail:
|
||||
printf("\033[32m[DETAIL]\033[39m %s\033[K\n", msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void NServFrontend::PrintSkip()
|
||||
{
|
||||
#ifdef WIN32
|
||||
printf(".....\n");
|
||||
#else
|
||||
printf(".....\033[K\n");
|
||||
#endif
|
||||
}
|
||||
48
daemon/nserv/NServFrontend.h
Normal file
48
daemon/nserv/NServFrontend.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NSERVFRONTEND_H
|
||||
#define NSERVFRONTEND_H
|
||||
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
|
||||
class NServFrontend : public Thread
|
||||
{
|
||||
public:
|
||||
NServFrontend();
|
||||
|
||||
private:
|
||||
uint32 m_neededLogEntries = 0;
|
||||
uint32 m_neededLogFirstId = 0;
|
||||
bool m_needGoBack = false;
|
||||
|
||||
#ifdef WIN32
|
||||
HANDLE m_console;
|
||||
#endif
|
||||
|
||||
void Run();
|
||||
void Update();
|
||||
void BeforePrint();
|
||||
void PrintMessage(Message& message);
|
||||
void PrintSkip();
|
||||
};
|
||||
|
||||
#endif
|
||||
280
daemon/nserv/NServMain.cpp
Normal file
280
daemon/nserv/NServMain.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
|
||||
#include "Thread.h"
|
||||
#include "Connection.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "NServFrontend.h"
|
||||
#include "NntpServer.h"
|
||||
#include "NzbGenerator.h"
|
||||
#include "Options.h"
|
||||
|
||||
struct NServOpts
|
||||
{
|
||||
CString dataDir;
|
||||
CString cacheDir;
|
||||
CString bindAddress;
|
||||
int firstPort;
|
||||
int instances;
|
||||
CString logFile;
|
||||
CString secureCert;
|
||||
CString secureKey;
|
||||
BString<1024> logOpt;
|
||||
bool generateNzb;
|
||||
int segmentSize;
|
||||
bool quit;
|
||||
int latency;
|
||||
int speed;
|
||||
bool memCache;
|
||||
bool paramError;
|
||||
|
||||
NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts);
|
||||
};
|
||||
|
||||
void NServPrintUsage(const char* com);
|
||||
|
||||
int NServMain(int argc, char* argv[])
|
||||
{
|
||||
Log log;
|
||||
|
||||
info("NServ %s (Test NNTP server)", Util::VersionRevision());
|
||||
|
||||
Options::CmdOptList cmdOpts;
|
||||
NServOpts opts(argc, argv, cmdOpts);
|
||||
|
||||
if (opts.dataDir.Empty() || opts.paramError)
|
||||
{
|
||||
NServPrintUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!FileSystem::DirectoryExists(opts.dataDir))
|
||||
{
|
||||
// dataDir does not exist. Let's find out a bit more, and report:
|
||||
if (FileSystem::FileExists(opts.dataDir))
|
||||
{
|
||||
error("Specified data-dir %s is not a directory, but a file", *opts.dataDir );
|
||||
} else {
|
||||
error("Specified data-dir %s does not exist", *opts.dataDir );
|
||||
}
|
||||
}
|
||||
|
||||
Options options(&cmdOpts, nullptr);
|
||||
|
||||
log.InitOptions();
|
||||
Thread::Init();
|
||||
Connection::Init();
|
||||
#ifndef DISABLE_TLS
|
||||
TlsSocket::Init();
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
NServFrontend frontend;
|
||||
frontend.Start();
|
||||
|
||||
if (opts.generateNzb)
|
||||
{
|
||||
NzbGenerator gen(opts.dataDir, opts.segmentSize);
|
||||
gen.Execute();
|
||||
if (opts.quit)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CString errmsg;
|
||||
if (opts.cacheDir && !FileSystem::ForceDirectories(opts.cacheDir, errmsg))
|
||||
{
|
||||
error("Could not create directory %s: %s", *opts.cacheDir, *errmsg);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<NntpServer>> instances;
|
||||
NntpCache cache;
|
||||
|
||||
for (int i = 0; i < opts.instances; i++)
|
||||
{
|
||||
instances.emplace_back(std::make_unique<NntpServer>(i + 1, opts.bindAddress,
|
||||
opts.firstPort + i, opts.secureCert, opts.secureKey, opts.dataDir, opts.cacheDir,
|
||||
opts.latency, opts.speed, opts.memCache ? &cache : nullptr));
|
||||
instances.back()->Start();
|
||||
}
|
||||
|
||||
info("Press Ctrl+C to quit");
|
||||
while (getchar()) Util::Sleep(200);
|
||||
|
||||
for (std::unique_ptr<NntpServer>& serv: instances)
|
||||
{
|
||||
serv->Stop();
|
||||
}
|
||||
frontend.Stop();
|
||||
|
||||
bool hasRunning = false;
|
||||
do
|
||||
{
|
||||
hasRunning = frontend.IsRunning();
|
||||
for (std::unique_ptr<NntpServer>& serv : instances)
|
||||
{
|
||||
hasRunning |= serv->IsRunning();
|
||||
}
|
||||
Util::Sleep(50);
|
||||
} while (hasRunning);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NServPrintUsage(const char* com)
|
||||
{
|
||||
printf("Usage:\n"
|
||||
" %s --nserv -d <data-dir> [optional switches] \n"
|
||||
" -d <data-dir> - directory whose files will be served\n"
|
||||
" Optional switches:\n"
|
||||
" -c <cache-dir> - directory to store encoded articles\n"
|
||||
" -m - in-memory cache (unlimited, use with care)\n"
|
||||
" -l <log-file> - write into log-file (disabled by default)\n"
|
||||
" -i <instances> - number of server instances (default is 1)\n"
|
||||
" -b <address> - ip address to bind to (default is 0.0.0.0)\n"
|
||||
" -p <port> - port number for the first instance (default is 6791)\n"
|
||||
" -s <cert> <key> - paths to SSL certificate and key files\n"
|
||||
" -v <verbose> - verbosity level 0..3 (default is 2)\n"
|
||||
" -w <msec> - response latency (in milliseconds)\n"
|
||||
" -r <KB/s> - speed throttling (in kilobytes per second)\n"
|
||||
" -z <seg-size> - generate nzbs for all files in data-dir (size in bytes)\n"
|
||||
" -q - quit after generating nzbs (in combination with -z)\n"
|
||||
, FileSystem::BaseFileName(com));
|
||||
}
|
||||
|
||||
NServOpts::NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts)
|
||||
{
|
||||
instances = 1;
|
||||
bindAddress = "0.0.0.0";
|
||||
firstPort = 6791;
|
||||
generateNzb = false;
|
||||
segmentSize = 500000;
|
||||
quit = false;
|
||||
latency = 0;
|
||||
memCache = false;
|
||||
speed = 0;
|
||||
paramError = false;
|
||||
int verbosity = 2;
|
||||
|
||||
char short_options[] = "b:c:d:l:p:i:ms:v:w:r:z:q";
|
||||
|
||||
optind = 2;
|
||||
while (true)
|
||||
{
|
||||
int c = getopt(argc, argv, short_options);
|
||||
if (c == -1) break;
|
||||
switch (c)
|
||||
{
|
||||
case 'd':
|
||||
dataDir = optind > argc ? nullptr : argv[optind - 1];
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
cacheDir = optind > argc ? nullptr : argv[optind - 1];
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
memCache = true;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
logFile = optind > argc ? nullptr : argv[optind - 1];
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
bindAddress= optind > argc ? "0.0.0.0" : argv[optind - 1];
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
firstPort = atoi(optind > argc ? "6791" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
secureCert = optind > argc ? nullptr : argv[optind - 1];
|
||||
optind++;
|
||||
secureKey = optind > argc ? nullptr : argv[optind - 1];
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
instances = atoi(optind > argc ? "1" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbosity = atoi(optind > argc ? "1" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
latency = atoi(optind > argc ? "0" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
speed = atoi(optind > argc ? "0" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
generateNzb = true;
|
||||
segmentSize = atoi(optind > argc ? "500000" : argv[optind - 1]);
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
quit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
{
|
||||
paramError = true;
|
||||
}
|
||||
|
||||
if (logFile.Empty())
|
||||
{
|
||||
cmdOpts.push_back("WriteLog=none");
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdOpts.push_back("WriteLog=append");
|
||||
logOpt.Format("LogFile=%s", *logFile);
|
||||
cmdOpts.push_back(logOpt);
|
||||
}
|
||||
|
||||
if (verbosity < 1)
|
||||
{
|
||||
cmdOpts.push_back("InfoTarget=none");
|
||||
cmdOpts.push_back("WarningTarget=none");
|
||||
cmdOpts.push_back("ErrorTarget=none");
|
||||
}
|
||||
if (verbosity < 2)
|
||||
{
|
||||
cmdOpts.push_back("DetailTarget=none");
|
||||
}
|
||||
if (verbosity > 2)
|
||||
{
|
||||
cmdOpts.push_back("DebugTarget=both");
|
||||
}
|
||||
}
|
||||
26
daemon/nserv/NServMain.h
Normal file
26
daemon/nserv/NServMain.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NSERVMAIN_H
|
||||
#define NSERVMAIN_H
|
||||
|
||||
int NServMain(int argc, char * argv[]);
|
||||
|
||||
#endif
|
||||
443
daemon/nserv/NntpServer.cpp
Normal file
443
daemon/nserv/NntpServer.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NntpServer.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "YEncoder.h"
|
||||
|
||||
class NntpProcessor : public Thread
|
||||
{
|
||||
public:
|
||||
NntpProcessor(int id, int serverId, const char* dataDir, const char* cacheDir,
|
||||
const char* secureCert, const char* secureKey, int latency, int speed, NntpCache* cache) :
|
||||
m_id(id), m_serverId(serverId), m_dataDir(dataDir), m_cacheDir(cacheDir),
|
||||
m_secureCert(secureCert), m_secureKey(secureKey), m_latency(latency),
|
||||
m_speed(speed), m_cache(cache) {}
|
||||
~NntpProcessor() { m_connection->Disconnect(); }
|
||||
virtual void Run();
|
||||
void SetConnection(std::unique_ptr<Connection>&& connection) { m_connection = std::move(connection); }
|
||||
|
||||
private:
|
||||
int m_id;
|
||||
int m_serverId;
|
||||
std::unique_ptr<Connection> m_connection;
|
||||
const char* m_dataDir;
|
||||
const char* m_cacheDir;
|
||||
const char* m_secureCert;
|
||||
const char* m_secureKey;
|
||||
int m_latency;
|
||||
int m_speed;
|
||||
const char* m_messageid;
|
||||
CString m_filename;
|
||||
int m_part;
|
||||
int64 m_offset;
|
||||
int m_size;
|
||||
bool m_sendHeaders;
|
||||
int64 m_start;
|
||||
NntpCache* m_cache;
|
||||
|
||||
void ServArticle();
|
||||
void SendSegment();
|
||||
bool ServerInList(const char* servList);
|
||||
void SendData(const char* buffer, int size);
|
||||
};
|
||||
|
||||
|
||||
void NntpServer::Run()
|
||||
{
|
||||
debug("Entering NntpServer-loop");
|
||||
|
||||
info("Listening on port %i", m_port);
|
||||
|
||||
#ifdef WIN32
|
||||
if (m_speed > 0)
|
||||
{
|
||||
timeBeginPeriod(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
int num = 1;
|
||||
|
||||
while (!IsStopped())
|
||||
{
|
||||
bool bind = true;
|
||||
|
||||
if (!m_connection)
|
||||
{
|
||||
m_connection = std::make_unique<Connection>(m_host, m_port, m_secureCert);
|
||||
m_connection->SetTimeout(10);
|
||||
m_connection->SetSuppressErrors(false);
|
||||
bind = m_connection->Bind();
|
||||
}
|
||||
|
||||
// Accept connections and store the new Connection
|
||||
std::unique_ptr<Connection> acceptedConnection;
|
||||
if (bind)
|
||||
{
|
||||
acceptedConnection = m_connection->Accept();
|
||||
}
|
||||
if (!bind || !acceptedConnection)
|
||||
{
|
||||
// Server could not bind or accept connection, waiting 1/2 sec and try again
|
||||
if (IsStopped())
|
||||
{
|
||||
break;
|
||||
}
|
||||
m_connection.reset();
|
||||
Util::Sleep(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
NntpProcessor* commandThread = new NntpProcessor(num++, m_id, m_dataDir,
|
||||
m_cacheDir, m_secureCert, m_secureKey, m_latency, m_speed, m_cache);
|
||||
commandThread->SetAutoDestroy(true);
|
||||
commandThread->SetConnection(std::move(acceptedConnection));
|
||||
commandThread->Start();
|
||||
}
|
||||
|
||||
if (m_connection)
|
||||
{
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
|
||||
debug("Exiting NntpServer-loop");
|
||||
}
|
||||
|
||||
void NntpServer::Stop()
|
||||
{
|
||||
Thread::Stop();
|
||||
if (m_connection)
|
||||
{
|
||||
m_connection->SetSuppressErrors(true);
|
||||
m_connection->Cancel();
|
||||
#ifdef WIN32
|
||||
m_connection->Disconnect();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NntpProcessor::Run()
|
||||
{
|
||||
m_connection->SetSuppressErrors(false);
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
if (m_secureCert && !m_connection->StartTls(false, m_secureCert, m_secureKey))
|
||||
{
|
||||
error("Could not establish secure connection to nntp-client: Start TLS failed");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
info("[%i] Incoming connection from: %s", m_id, m_connection->GetHost() );
|
||||
m_connection->WriteLine("200 Welcome (NServ)\r\n");
|
||||
|
||||
CharBuffer buf(1024);
|
||||
int bytesRead = 0;
|
||||
while (CString line = m_connection->ReadLine(buf, 1024, &bytesRead))
|
||||
{
|
||||
line.TrimRight();
|
||||
detail("[%i] Received: %s", m_id, *line);
|
||||
|
||||
if (!strncasecmp(line, "ARTICLE ", 8))
|
||||
{
|
||||
m_messageid = line + 8;
|
||||
m_sendHeaders = true;
|
||||
ServArticle();
|
||||
}
|
||||
else if (!strncasecmp(line, "BODY ", 5))
|
||||
{
|
||||
m_messageid = line + 5;
|
||||
m_sendHeaders = false;
|
||||
ServArticle();
|
||||
}
|
||||
else if (!strncasecmp(line, "GROUP ", 6))
|
||||
{
|
||||
m_connection->WriteLine(CString::FormatStr("211 0 0 0 %s\r\n", line + 6));
|
||||
}
|
||||
else if (!strncasecmp(line, "AUTHINFO ", 9))
|
||||
{
|
||||
m_connection->WriteLine("281 Authentication accepted\r\n");
|
||||
}
|
||||
else if (!strcasecmp(line, "QUIT"))
|
||||
{
|
||||
detail("[%i] Closing connection", m_id);
|
||||
m_connection->WriteLine("205 Connection closing\r\n");
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
warn("[%i] Unknown command: %s", m_id, *line);
|
||||
m_connection->WriteLine("500 Unknown command\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
m_connection->SetGracefull(true);
|
||||
m_connection->Disconnect();
|
||||
}
|
||||
|
||||
/*
|
||||
Message-id format:
|
||||
<file-path-relative-to-dataDir?xxx=yyy:zzz!1,2,3>
|
||||
where:
|
||||
xxx - part number (integer)
|
||||
xxx - offset from which to read the files (integer)
|
||||
yyy - size of file block to return (integer)
|
||||
1,2,3 - list of server ids, which have the article (optional),
|
||||
if the list is given and current server is not in the list
|
||||
the "article not found"-error is returned.
|
||||
Examples:
|
||||
<parchecker/testfile.dat?1=0:50000> - return first 50000 bytes starting from beginning
|
||||
<parchecker/testfile.dat?2=50000:50000> - return 50000 bytes starting from offset 50000
|
||||
<parchecker/testfile.dat?2=50000:50000!2> - article is missing on server 1
|
||||
*/
|
||||
void NntpProcessor::ServArticle()
|
||||
{
|
||||
detail("[%i] Serving: %s", m_id, m_messageid);
|
||||
|
||||
if (m_latency)
|
||||
{
|
||||
Util::Sleep(m_latency);
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
|
||||
const char* from = strchr(m_messageid, '?');
|
||||
const char* off = strchr(m_messageid, '=');
|
||||
const char* to = strchr(m_messageid, ':');
|
||||
const char* end = strchr(m_messageid, '>');
|
||||
const char* serv = strchr(m_messageid, '!');
|
||||
|
||||
if (from && off && to && end)
|
||||
{
|
||||
m_filename.Set(m_messageid + 1, (int)(from - m_messageid - 1));
|
||||
m_part = atoi(from + 1);
|
||||
m_offset = atoll(off + 1);
|
||||
m_size = atoi(to + 1);
|
||||
|
||||
ok = !serv || ServerInList(serv + 1);
|
||||
|
||||
if (ok)
|
||||
{
|
||||
SendSegment();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
m_connection->WriteLine("430 No Such Article Found\r\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_connection->WriteLine("430 No Such Article Found (invalid message id format)\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool NntpProcessor::ServerInList(const char* servList)
|
||||
{
|
||||
Tokenizer tok(servList, ",");
|
||||
while (const char* servid = tok.Next())
|
||||
{
|
||||
if (atoi(servid) == m_serverId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NntpProcessor::SendSegment()
|
||||
{
|
||||
detail("[%i] Sending segment %s (%i=%" PRIi64 ":%i)", m_id, *m_filename, m_part, m_offset, m_size);
|
||||
|
||||
if (m_speed > 0)
|
||||
{
|
||||
m_start = Util::CurrentTicks();
|
||||
}
|
||||
|
||||
BString<1024> fullFilename("%s/%s", m_dataDir, *m_filename);
|
||||
BString<1024> cacheFileDir("%s/%s", m_cacheDir, *m_filename);
|
||||
BString<1024> cacheFileName("%i=%" PRIi64 "-%i", m_part, m_offset, m_size);
|
||||
BString<1024> cacheFullFilename("%s/%s", *cacheFileDir, *cacheFileName);
|
||||
BString<1024> cacheKey("%s/%s", *m_filename, *cacheFileName);
|
||||
|
||||
const char* cachedData = nullptr;
|
||||
int cachedSize;
|
||||
if (m_cache)
|
||||
{
|
||||
m_cache->Find(cacheKey, cachedData, cachedSize);
|
||||
}
|
||||
|
||||
DiskFile cacheFile;
|
||||
bool readCache = !cachedData && m_cacheDir && cacheFile.Open(cacheFullFilename, DiskFile::omRead);
|
||||
bool writeCache = !cachedData && m_cacheDir && !readCache;
|
||||
StringBuilder cacheMem;
|
||||
if (m_cache && !cachedData)
|
||||
{
|
||||
cacheMem.Reserve((int)(m_size * 1.1));
|
||||
}
|
||||
|
||||
CString errmsg;
|
||||
if (writeCache && !FileSystem::ForceDirectories(cacheFileDir, errmsg))
|
||||
{
|
||||
error("Could not create directory %s: %s", *cacheFileDir, *errmsg);
|
||||
}
|
||||
|
||||
if (writeCache && !cacheFile.Open(cacheFullFilename, DiskFile::omWrite))
|
||||
{
|
||||
error("Could not create file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
|
||||
}
|
||||
|
||||
if (!cachedData && !readCache && !FileSystem::FileExists(fullFilename))
|
||||
{
|
||||
m_connection->WriteLine(CString::FormatStr("430 Article not found\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
YEncoder encoder(fullFilename, m_part, m_offset, m_size,
|
||||
[proc = this, writeCache, &cacheFile, &cacheMem](const char* buf, int size)
|
||||
{
|
||||
if (proc->m_cache)
|
||||
{
|
||||
cacheMem.Append(buf);
|
||||
}
|
||||
if (writeCache)
|
||||
{
|
||||
cacheFile.Write(buf, size);
|
||||
}
|
||||
proc->SendData(buf, size);
|
||||
});
|
||||
|
||||
if (!cachedData && !readCache && !encoder.OpenFile(errmsg))
|
||||
{
|
||||
m_connection->WriteLine(CString::FormatStr("403 %s\r\n", *errmsg));
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->WriteLine(CString::FormatStr("%i, 0 %s\r\n", m_sendHeaders ? 222 : 220, m_messageid));
|
||||
if (m_sendHeaders)
|
||||
{
|
||||
m_connection->WriteLine(CString::FormatStr("Message-ID: %s\r\n", m_messageid));
|
||||
m_connection->WriteLine(CString::FormatStr("Subject: \"%s\"\r\n", FileSystem::BaseFileName(m_filename)));
|
||||
m_connection->WriteLine("\r\n");
|
||||
}
|
||||
|
||||
if (cachedData)
|
||||
{
|
||||
SendData(cachedData, cachedSize);
|
||||
}
|
||||
else if (readCache)
|
||||
{
|
||||
cacheFile.Seek(0, DiskFile::soEnd);
|
||||
int size = (int)cacheFile.Position();
|
||||
CharBuffer buf(size);
|
||||
cacheFile.Seek(0);
|
||||
if (cacheFile.Read((char*)buf, size) != size)
|
||||
{
|
||||
error("Could not read file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
|
||||
}
|
||||
if (m_cache)
|
||||
{
|
||||
cacheMem.Append(buf, size);
|
||||
}
|
||||
SendData(buf, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoder.WriteSegment();
|
||||
}
|
||||
|
||||
if (!cachedData && cacheMem.Length() > 0)
|
||||
{
|
||||
m_cache->Append(cacheKey, cacheMem, cacheMem.Length());
|
||||
}
|
||||
|
||||
m_connection->WriteLine(".\r\n");
|
||||
}
|
||||
|
||||
void NntpProcessor::SendData(const char* buffer, int size)
|
||||
{
|
||||
if (m_speed == 0)
|
||||
{
|
||||
m_connection->Send(buffer, size);
|
||||
return;
|
||||
}
|
||||
|
||||
int64 expectedTime = (int64)1000 * size / (m_speed * 1024) - (Util::CurrentTicks() - m_start) / 1000;
|
||||
|
||||
int chunkNum = 21;
|
||||
int chunkSize = size;
|
||||
int pause = 0;
|
||||
|
||||
while (pause < 1 && chunkNum > 1)
|
||||
{
|
||||
chunkNum--;
|
||||
chunkSize = size / chunkNum;
|
||||
pause = (int)(expectedTime / chunkNum);
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
for (int i = 0; i < chunkNum; i++)
|
||||
{
|
||||
int len = sent + chunkSize < size ? chunkSize : size - sent;
|
||||
|
||||
while (sent + len < size && *(buffer + sent + len) != '\r')
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
m_connection->Send(buffer + sent, len);
|
||||
int64 now = Util::CurrentTicks();
|
||||
if (now + pause * 1000 < m_start + expectedTime * 1000)
|
||||
{
|
||||
Util::Sleep(pause);
|
||||
}
|
||||
sent += len;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NntpCache::Append(const char* key, const char* data, int len)
|
||||
{
|
||||
Guard guard(m_lock);
|
||||
if (!len)
|
||||
{
|
||||
len = strlen(data);
|
||||
}
|
||||
m_items.emplace(key, std::make_unique<CacheItem>(key, data, len));
|
||||
}
|
||||
|
||||
bool NntpCache::Find(const char* key, const char*& data, int& size)
|
||||
{
|
||||
Guard guard(m_lock);
|
||||
|
||||
CacheMap::iterator pos = m_items.find(key);
|
||||
if (pos != m_items.end())
|
||||
{
|
||||
data = (*pos).second->m_data;
|
||||
size = (*pos).second->m_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
78
daemon/nserv/NntpServer.h
Normal file
78
daemon/nserv/NntpServer.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NNTPSERVER_H
|
||||
#define NNTPSERVER_H
|
||||
|
||||
#include "Thread.h"
|
||||
#include "Connection.h"
|
||||
#include "Util.h"
|
||||
|
||||
class NntpCache
|
||||
{
|
||||
public:
|
||||
void Append(const char* key, const char* data, int len = 0);
|
||||
bool Find(const char* key, const char*& data, int& size);
|
||||
|
||||
private:
|
||||
class CacheItem
|
||||
{
|
||||
public:
|
||||
CacheItem(const char* key, const char* data, int size) :
|
||||
m_key(key), m_data(data), m_size(size) {}
|
||||
|
||||
CString m_key;
|
||||
CString m_data;
|
||||
int m_size = 0;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::string, std::unique_ptr<CacheItem>> CacheMap;
|
||||
|
||||
CacheMap m_items;
|
||||
Mutex m_lock;
|
||||
};
|
||||
|
||||
class NntpServer : public Thread
|
||||
{
|
||||
public:
|
||||
NntpServer(int id, const char* host, int port, const char* secureCert,
|
||||
const char* secureKey, const char* dataDir, const char* cacheDir,
|
||||
int latency, int speed, NntpCache* cache) :
|
||||
m_id(id), m_host(host), m_port(port), m_secureCert(secureCert),
|
||||
m_secureKey(secureKey), m_dataDir(dataDir), m_cacheDir(cacheDir),
|
||||
m_latency(latency), m_speed(speed), m_cache(cache) {}
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
|
||||
private:
|
||||
int m_id;
|
||||
CString m_host;
|
||||
int m_port;
|
||||
CString m_secureCert;
|
||||
CString m_secureKey;
|
||||
std::unique_ptr<Connection> m_connection;
|
||||
CString m_dataDir;
|
||||
CString m_cacheDir;
|
||||
int m_latency;
|
||||
int m_speed;
|
||||
NntpCache* m_cache;
|
||||
};
|
||||
|
||||
#endif
|
||||
131
daemon/nserv/NzbGenerator.cpp
Normal file
131
daemon/nserv/NzbGenerator.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "NzbGenerator.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Log.h"
|
||||
|
||||
void NzbGenerator::Execute()
|
||||
{
|
||||
info("Generating nzbs for %s", *m_dataDir);
|
||||
|
||||
DirBrowser dir(m_dataDir);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
BString<1024> fullFilename("%s%c%s", *m_dataDir, PATH_SEPARATOR, filename);
|
||||
|
||||
int len = strlen(filename);
|
||||
if (len > 4 && !strcasecmp(filename + len - 4, ".nzb"))
|
||||
{
|
||||
// skip nzb-files
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateNzb(fullFilename);
|
||||
}
|
||||
|
||||
info("Nzb generation finished");
|
||||
}
|
||||
|
||||
void NzbGenerator::GenerateNzb(const char* path)
|
||||
{
|
||||
BString<1024> nzbFilename("%s%c%s.nzb", *m_dataDir, PATH_SEPARATOR, FileSystem::BaseFileName(path));
|
||||
|
||||
if (FileSystem::FileExists(nzbFilename))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info("Generating nzb for %s", FileSystem::BaseFileName(path));
|
||||
|
||||
DiskFile outfile;
|
||||
if (!outfile.Open(nzbFilename, DiskFile::omWrite))
|
||||
{
|
||||
error("Could not create file %s", *nzbFilename);
|
||||
return;
|
||||
}
|
||||
|
||||
outfile.Print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
outfile.Print("<!DOCTYPE nzb PUBLIC \"-//newzBin//DTD NZB 1.0//EN\" \"http://www.newzbin.com/DTD/nzb/nzb-1.0.dtd\">\n");
|
||||
outfile.Print("<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\n");
|
||||
|
||||
bool isDir = FileSystem::DirectoryExists(path);
|
||||
if (isDir)
|
||||
{
|
||||
AppendDir(outfile, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendFile(outfile, path, nullptr);
|
||||
}
|
||||
|
||||
outfile.Print("</nzb>\n");
|
||||
|
||||
outfile.Close();
|
||||
}
|
||||
|
||||
void NzbGenerator::AppendDir(DiskFile& outfile, const char* path)
|
||||
{
|
||||
DirBrowser dir(path);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
BString<1024> fullFilename("%s%c%s", path, PATH_SEPARATOR, filename);
|
||||
|
||||
bool isDir = FileSystem::DirectoryExists(fullFilename);
|
||||
if (!isDir)
|
||||
{
|
||||
AppendFile(outfile, fullFilename, FileSystem::BaseFileName(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NzbGenerator::AppendFile(DiskFile& outfile, const char* filename, const char* relativePath)
|
||||
{
|
||||
detail("Processing %s", FileSystem::BaseFileName(filename));
|
||||
|
||||
int64 fileSize = FileSystem::FileSize(filename);
|
||||
time_t timestamp = Util::CurrentTime();
|
||||
|
||||
int segmentCount = (int)((fileSize + m_segmentSize - 1) / m_segmentSize);
|
||||
|
||||
outfile.Print("<file poster=\"nserv\" date=\"%i\" subject=\""%s" yEnc (1/%i)\">\n",
|
||||
(int)timestamp, FileSystem::BaseFileName(filename), segmentCount);
|
||||
outfile.Print("<groups>\n");
|
||||
outfile.Print("<group>alt.binaries.test</group>\n");
|
||||
outfile.Print("</groups>\n");
|
||||
outfile.Print("<segments>\n");
|
||||
|
||||
int64 segOffset = 0;
|
||||
for (int segno = 1; segno <= segmentCount; segno++)
|
||||
{
|
||||
int segSize = (int)(segOffset + m_segmentSize < fileSize ? m_segmentSize : fileSize - segOffset);
|
||||
outfile.Print("<segment bytes=\"%i\" number=\"%i\">%s%s%s?%i=%" PRIi64 ":%i</segment>\n",
|
||||
m_segmentSize, segno,
|
||||
relativePath ? relativePath : "",
|
||||
relativePath ? "/" : "",
|
||||
FileSystem::BaseFileName(filename), segno, segOffset, segSize);
|
||||
segOffset += segSize;
|
||||
}
|
||||
|
||||
outfile.Print("</segments>\n");
|
||||
outfile.Print("</file>\n");
|
||||
}
|
||||
43
daemon/nserv/NzbGenerator.h
Normal file
43
daemon/nserv/NzbGenerator.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NZBGENERATOR_H
|
||||
#define NZBGENERATOR_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
class NzbGenerator
|
||||
{
|
||||
public:
|
||||
NzbGenerator(const char* dataDir, int segmentSize) :
|
||||
m_dataDir(dataDir), m_segmentSize(segmentSize) {};
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CString m_dataDir;
|
||||
int m_segmentSize;
|
||||
|
||||
void GenerateNzb(const char* path);
|
||||
void AppendFile(DiskFile& outfile, const char* filename, const char* relativePath);
|
||||
void AppendDir(DiskFile& outfile, const char* path);
|
||||
};
|
||||
|
||||
#endif
|
||||
129
daemon/nserv/YEncoder.cpp
Normal file
129
daemon/nserv/YEncoder.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "YEncoder.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Log.h"
|
||||
|
||||
bool YEncoder::OpenFile(CString& errmsg)
|
||||
{
|
||||
if (m_size < 0)
|
||||
{
|
||||
errmsg = "Invalid segment size";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_diskfile.Open(m_filename, DiskFile::omRead) || !m_diskfile.Seek(0, DiskFile::soEnd))
|
||||
{
|
||||
errmsg = "File not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_fileSize = m_diskfile.Position();
|
||||
if (m_size == 0)
|
||||
{
|
||||
m_size = (int)(m_fileSize - m_offset + 1);
|
||||
}
|
||||
|
||||
if (m_fileSize < m_offset + m_size)
|
||||
{
|
||||
errmsg = "Invalid segment size";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_diskfile.Seek(m_offset))
|
||||
{
|
||||
errmsg = "Invalid segment offset";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void YEncoder::WriteSegment()
|
||||
{
|
||||
StringBuilder outbuf;
|
||||
outbuf.Reserve(std::max(2048, std::min((int)(m_size * 1.1), 16 * 1024 * 1024)));
|
||||
|
||||
outbuf.Append(CString::FormatStr("=ybegin part=%i line=128 size=%" PRIi64 " name=%s\r\n", m_part, m_fileSize, FileSystem::BaseFileName(m_filename)));
|
||||
outbuf.Append(CString::FormatStr("=ypart begin=%" PRIi64 " end=%" PRIi64 "\r\n", m_offset + 1, m_offset + m_size));
|
||||
|
||||
Crc32 crc;
|
||||
CharBuffer inbuf(std::min(m_size, 16 * 1024 * 1024));
|
||||
int lnsz = 0;
|
||||
char* out = (char*)outbuf + outbuf.Length();
|
||||
|
||||
while (m_diskfile.Position() < m_offset + m_size)
|
||||
{
|
||||
int64 needBytes = std::min((int64)inbuf.Size(), m_offset + m_size - m_diskfile.Position());
|
||||
int64 readBytes = m_diskfile.Read(inbuf, needBytes);
|
||||
bool lastblock = m_diskfile.Position() == m_offset + m_size;
|
||||
if (readBytes == 0)
|
||||
{
|
||||
return; // error;
|
||||
}
|
||||
|
||||
crc.Append((uchar*)(const char*)inbuf, (int)readBytes);
|
||||
|
||||
char* in = inbuf;
|
||||
while (readBytes > 0)
|
||||
{
|
||||
char ch = *in++;
|
||||
readBytes--;
|
||||
ch = (char)(((uchar)(ch) + 42) % 256);
|
||||
if (ch == '\0' || ch == '\n' || ch == '\r' || ch == '=' || ch == ' ' || ch == '\t')
|
||||
{
|
||||
*out++ = '=';
|
||||
lnsz++;
|
||||
ch = (char)(((uchar)ch + 64) % 256);
|
||||
}
|
||||
if (ch == '.' && lnsz == 0)
|
||||
{
|
||||
*out++ = '.';
|
||||
lnsz++;
|
||||
}
|
||||
*out++ = ch;
|
||||
lnsz++;
|
||||
|
||||
if (lnsz >= 128 || (readBytes == 0 && lastblock))
|
||||
{
|
||||
*out++ = '\r';
|
||||
*out++ = '\n';
|
||||
lnsz += 2;
|
||||
outbuf.SetLength(outbuf.Length() + lnsz);
|
||||
|
||||
if (outbuf.Length() > outbuf.Capacity() - 200)
|
||||
{
|
||||
m_writeFunc(outbuf, outbuf.Length());
|
||||
outbuf.SetLength(0);
|
||||
out = (char*)outbuf;
|
||||
}
|
||||
|
||||
lnsz = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_diskfile.Close();
|
||||
|
||||
outbuf.Append(CString::FormatStr("=yend size=%i part=0 pcrc32=%08x\r\n", m_size, (unsigned int)crc.Finish()));
|
||||
m_writeFunc(outbuf, outbuf.Length());
|
||||
}
|
||||
47
daemon/nserv/YEncoder.h
Normal file
47
daemon/nserv/YEncoder.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef YENCODER_H
|
||||
#define YENCODER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
class YEncoder
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(const char* buf, int size)> WriteFunc;
|
||||
|
||||
YEncoder(const char* filename, int part, int64 offset, int size, WriteFunc writeFunc) :
|
||||
m_filename(filename), m_part(part), m_offset(offset), m_size(size), m_writeFunc(writeFunc) {};
|
||||
bool OpenFile(CString& errmsg);
|
||||
void WriteSegment();
|
||||
|
||||
private:
|
||||
DiskFile m_diskfile;
|
||||
CString m_filename;
|
||||
int m_part;
|
||||
int64 m_offset;
|
||||
int m_size;
|
||||
int64 m_fileSize;
|
||||
WriteFunc m_writeFunc;
|
||||
};
|
||||
|
||||
#endif
|
||||
227
daemon/postprocess/Cleanup.cpp
Normal file
227
daemon/postprocess/Cleanup.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Cleanup.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "ParParser.h"
|
||||
#include "Options.h"
|
||||
|
||||
void MoveController::StartJob(PostInfo* postInfo)
|
||||
{
|
||||
MoveController* moveController = new MoveController();
|
||||
moveController->m_postInfo = postInfo;
|
||||
moveController->SetAutoDestroy(false);
|
||||
|
||||
postInfo->SetPostThread(moveController);
|
||||
|
||||
moveController->Start();
|
||||
}
|
||||
|
||||
void MoveController::Run()
|
||||
{
|
||||
BString<1024> nzbName;
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
nzbName = m_postInfo->GetNzbInfo()->GetName();
|
||||
m_interDir = m_postInfo->GetNzbInfo()->GetDestDir();
|
||||
m_destDir = m_postInfo->GetNzbInfo()->GetFinalDir();
|
||||
}
|
||||
|
||||
BString<1024> infoName("move for %s", *nzbName);
|
||||
SetInfoName(infoName);
|
||||
|
||||
if (m_destDir.Empty())
|
||||
{
|
||||
m_destDir = m_postInfo->GetNzbInfo()->BuildFinalDirName();
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Moving completed files for %s", *nzbName);
|
||||
|
||||
bool ok = MoveFiles();
|
||||
|
||||
infoName[0] = 'M'; // uppercase
|
||||
|
||||
if (ok)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "%s successful", *infoName);
|
||||
// save new dest dir
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
m_postInfo->GetNzbInfo()->SetDestDir(m_destDir);
|
||||
m_postInfo->GetNzbInfo()->SetFinalDir("");
|
||||
m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkError, "%s failed", *infoName);
|
||||
m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msFailure);
|
||||
}
|
||||
|
||||
m_postInfo->SetWorking(false);
|
||||
}
|
||||
|
||||
bool MoveController::MoveFiles()
|
||||
{
|
||||
CString errmsg;
|
||||
if (!FileSystem::ForceDirectories(m_destDir, errmsg))
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not create directory %s: %s", *m_destDir, *errmsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
{
|
||||
DirBrowser dir(m_interDir);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
BString<1024> srcFile("%s%c%s",* m_interDir, PATH_SEPARATOR, filename);
|
||||
CString dstFile = FileSystem::MakeUniqueFilename(m_destDir, FileSystem::MakeValidFilename(filename));
|
||||
bool hiddenFile = filename[0] == '.';
|
||||
|
||||
if (!hiddenFile)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Moving file %s to %s", FileSystem::BaseFileName(srcFile), *m_destDir);
|
||||
}
|
||||
|
||||
if (!FileSystem::MoveFile(srcFile, dstFile) && !hiddenFile)
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not move file %s to %s: %s",
|
||||
*srcFile, *dstFile, *FileSystem::GetLastErrorMessage());
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
} // make sure "DirBrowser dir" is destroyed (and has closed its handle) before we trying to delete the directory
|
||||
|
||||
if (ok && !FileSystem::DeleteDirectoryWithContent(m_interDir, errmsg))
|
||||
{
|
||||
PrintMessage(Message::mkWarning, "Could not delete intermediate directory %s: %s", *m_interDir, *errmsg);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void MoveController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
|
||||
}
|
||||
|
||||
void CleanupController::StartJob(PostInfo* postInfo)
|
||||
{
|
||||
CleanupController* cleanupController = new CleanupController();
|
||||
cleanupController->m_postInfo = postInfo;
|
||||
cleanupController->SetAutoDestroy(false);
|
||||
|
||||
postInfo->SetPostThread(cleanupController);
|
||||
|
||||
cleanupController->Start();
|
||||
}
|
||||
|
||||
void CleanupController::Run()
|
||||
{
|
||||
BString<1024> nzbName;
|
||||
CString destDir;
|
||||
CString finalDir;
|
||||
{
|
||||
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
||||
nzbName = m_postInfo->GetNzbInfo()->GetName();
|
||||
destDir = m_postInfo->GetNzbInfo()->GetDestDir();
|
||||
finalDir = m_postInfo->GetNzbInfo()->GetFinalDir();
|
||||
}
|
||||
|
||||
BString<1024> infoName("cleanup for %s", *nzbName);
|
||||
SetInfoName(infoName);
|
||||
|
||||
PrintMessage(Message::mkInfo, "Cleaning up %s", *nzbName);
|
||||
|
||||
bool deleted = false;
|
||||
bool ok = Cleanup(destDir, &deleted);
|
||||
|
||||
if (ok && !finalDir.Empty())
|
||||
{
|
||||
bool deleted2 = false;
|
||||
ok = Cleanup(finalDir, &deleted2);
|
||||
deleted = deleted || deleted2;
|
||||
}
|
||||
|
||||
infoName[0] = 'C'; // uppercase
|
||||
|
||||
if (ok && deleted)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "%s successful", *infoName);
|
||||
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csSuccess);
|
||||
}
|
||||
else if (ok)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Nothing to cleanup for %s", *nzbName);
|
||||
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkError, "%s failed", *infoName);
|
||||
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csFailure);
|
||||
}
|
||||
|
||||
m_postInfo->SetWorking(false);
|
||||
}
|
||||
|
||||
bool CleanupController::Cleanup(const char* destDir, bool *deleted)
|
||||
{
|
||||
*deleted = false;
|
||||
bool ok = true;
|
||||
|
||||
DirBrowser dir(destDir);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
|
||||
|
||||
bool isDir = FileSystem::DirectoryExists(fullFilename);
|
||||
|
||||
if (isDir)
|
||||
{
|
||||
ok &= Cleanup(fullFilename, deleted);
|
||||
}
|
||||
|
||||
// check file extension
|
||||
bool deleteIt = Util::MatchFileExt(filename, g_Options->GetExtCleanupDisk(), ",;") && !isDir;
|
||||
|
||||
if (deleteIt)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Deleting file %s", filename);
|
||||
if (!FileSystem::DeleteFile(fullFilename))
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not delete file %s: %s", *fullFilename,
|
||||
*FileSystem::GetLastErrorMessage());
|
||||
ok = false;
|
||||
}
|
||||
|
||||
*deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void CleanupController::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user