mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-03 19:47:44 -05:00
Compare commits
587 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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
|
||||
63
.gitignore
vendored
Executable file
63
.gitignore
vendored
Executable file
@@ -0,0 +1,63 @@
|
||||
# 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.log
|
||||
config.status
|
||||
Makefile
|
||||
stamp-h1
|
||||
autom4te.cache/
|
||||
|
||||
# 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
|
||||
|
||||
# NZBGet specific
|
||||
nzbget
|
||||
code_revision.cpp
|
||||
36
.travis.yml
Normal file
36
.travis.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-5
|
||||
env: COMPILER=g++-5
|
||||
- compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.9
|
||||
env: COMPILER=g++-4.9
|
||||
- compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.6
|
||||
packages:
|
||||
- clang-3.6
|
||||
env: COMPILER=clang++-3.6
|
||||
|
||||
script:
|
||||
- $COMPILER --version
|
||||
- CXX=$COMPILER ./configure --enable-tests && make
|
||||
- ./nzbget --tests exclude:[DupeMatcher]
|
||||
559
ChangeLog
559
ChangeLog
@@ -1,22 +1,549 @@
|
||||
nzbget-17.0:
|
||||
- reworked the full source code base to utilize modern C++ features:
|
||||
- with the main motivation to make the code nicer and more fun to work
|
||||
with;
|
||||
- to compile NZBGet from source a compiler supporting C++14 is required;
|
||||
- most users don't have to compile on their own and can use official
|
||||
installers offered on download page;
|
||||
- for a detailed list of internal changes see Milestone "Modern C++" on
|
||||
GitHub;
|
||||
- now offering an official installer for FreeBSD:
|
||||
- automatic installation;
|
||||
- built-in update via web-interface;
|
||||
- currently supporting only x86_64 CPU architecture;
|
||||
- full support for Unicode and extra long file paths (more than 260 characters)
|
||||
on Windows including:
|
||||
- downloading;
|
||||
- par-verification and -repair;
|
||||
- par-renaming (deobfuscation);
|
||||
- unpacking;
|
||||
- post-processing;
|
||||
- added download volume quota management:
|
||||
- new options "MonthlyQuota", "QuotaStartDay", "DailyQuota";
|
||||
- downloading is suspended when the quota is reached and automatically
|
||||
resumed when the next billing period starts (month or day);
|
||||
- new fields in RPC-method "status": MonthSizeLo, MonthSizeHi,
|
||||
MonthSizeMB, DaySizeLo, DaySizeHi, DaySizeMB, QuotaReached.
|
||||
MonthSizes are related to current billing month taking option
|
||||
"QuotaStartDay" into account;
|
||||
- download volume for "this month" shown in web-interface in statistics
|
||||
dialog shows data for current billing month (taking option
|
||||
"QuotaStartDay" into account);
|
||||
- remaining time is shown in orange when the quota is reached;
|
||||
- dialog "statistics and status" may show extra row "Download quota:
|
||||
reached";
|
||||
- new function "Retry failed articles" in history:
|
||||
- failed downloads can be now tried again but in contrast to command
|
||||
"Download again" only failed articles are downloaded whereas
|
||||
successfully downloaded pieces are reused;
|
||||
- new command "HistoryRetryFailed" of RPC-method "editqueue";
|
||||
- new subcommand "F" of command line switch "-E/--edit" for history;
|
||||
- reworked options to delete already downloaded files when deleting downloads
|
||||
from queue:
|
||||
- removed option "DeleteCleanupDisk";
|
||||
- in the "Delete downloads confirmation dialog" allowing user to decide
|
||||
if the already downloaded files must be deleted or not;
|
||||
- option "HealthCheck" extended with new possible value "Park"; Health
|
||||
check now offers:
|
||||
- "Delete" - to move download into history and delete already
|
||||
downloaded files;
|
||||
- "Park" - to move download into history and keep already downloaded
|
||||
files;
|
||||
- remote command "GroupDelete" now always delete already downloaded files;
|
||||
- new remote command "GroupParkDelete" keeps already downloaded files;
|
||||
- new subcommand "DP" of console command "--edit/-E" to delete download
|
||||
from queue and keep already downloaded files;
|
||||
- new queue script event "NZB_MARKED"; new env var "NZBNA_MARKSTATUS" is
|
||||
passed to queue scripts:
|
||||
- new option "ShellOverride" allows to configure path to python, bash, etc.;
|
||||
useful on systems with non-standard paths; eliminating the need to change
|
||||
shebang for every script; also makes it possible to put scripts on non-exec
|
||||
file systems;
|
||||
- reduced disk fragmentation in direct write mode on Windows; this improves
|
||||
unpack speed;
|
||||
- news servers can now be configured as optional; marking server as optional
|
||||
tells NZBGet to ignore this server if a connection to this server cannot be
|
||||
established;
|
||||
- added support for tvdbid and tvmazeid in rss feeds;
|
||||
- added button to save nzb-log into a file directly from web-ui;
|
||||
- queue-scripts can now change destination after download is completed and
|
||||
before unpack;
|
||||
- queue-scripts save messages into nzb-log;
|
||||
- showing number of selected items in confirmation box when deleting or
|
||||
performing other actions on multiple items in web-interface;
|
||||
- built-in web-server can now use certificate chain files through option
|
||||
"SecureCert", when compiled using OpenSSL;
|
||||
- added support for SNI in TLS/SSL;
|
||||
- better error reporting when using GnuTLS;
|
||||
- allowing character "=" in dupe-badges;
|
||||
- par-check doesn't ignore files from option "ExtCleanupDisk" anymore; only
|
||||
files listed in option "ParIgnoreExt" are ignored;
|
||||
- low-level messages from par2-module are now added to log (as DETAIL);
|
||||
- option "ScriptDir" now accepts multiple directories;
|
||||
- path to original queued nzb-file is now passed to scripts;
|
||||
- hidden files and directories are now ignored by the scanner of incoming nzb
|
||||
directory;
|
||||
- separated disk state files for queue and history for better performance;
|
||||
- improved replacing of invalid characters in file names in certain cases;
|
||||
- automatically removing orphaned diskstate files from QueueDir;
|
||||
- added support for file names with reserved words on Windows;
|
||||
- added several settings to change behavior of web-interface, new section
|
||||
"WEB-INTERFACE" on settings page;
|
||||
- showing various status info in browser window title:
|
||||
- number of downloads, current speed, remaining time, pause state;
|
||||
- new option "WindowTitle";
|
||||
- improved tray icon (Windows) to look better on a dark background;
|
||||
- feed scripts must now return exit codes; this is to prevent queueing of
|
||||
all files from the feed if the feed script crashes;
|
||||
- improved error reporting on DNS lookup errors;
|
||||
- fixed: splitted files were not always joined;
|
||||
- fixed: wrong encoding in file names of downloaded files;
|
||||
- fixed: queue-scripts not called for failed URLs if the scripts were set in
|
||||
category’s option "PostScript";
|
||||
- fixed: crash when executing command "--printconfig";
|
||||
- fixed: error messages when trying to delete intermediate directory on Windows;
|
||||
- fixed: web-ui didn't load in Chrome on iOS;
|
||||
- improved POSIX configure script - now using pkg-config for all required
|
||||
libraries;
|
||||
- improved Windows installer - scripts are now installed into a subdirectory
|
||||
of default "MainDir" (C:\ProgramData\NZBGet\scripts) instead of program's
|
||||
directory;
|
||||
- updated and corrected option descriptions.
|
||||
|
||||
nzbget-16.4:
|
||||
- fixed resource (socket) leak which may cause "too many open files"
|
||||
errors with a possible crash.
|
||||
|
||||
nzbget-16.3:
|
||||
- fixed: certain downloads may fail due to a bug in the workaround
|
||||
implemented in v16.2.
|
||||
|
||||
nzbget-16.2:
|
||||
- implemented workaround to deal with malformed news server responses,
|
||||
which may still contain useful data.
|
||||
|
||||
nzbget-16.1:
|
||||
- fixed issues with reverse proxies;
|
||||
- fixed unpack failure on older AMD CPUs, when installed via
|
||||
universal Linux installer;
|
||||
- fixed: when the program was started from setup the default directories
|
||||
were created with wrong permission (Windows only).
|
||||
|
||||
nzbget-16.0:
|
||||
- moved project hosting to GitHub:
|
||||
- moved source code repository from subversion to git;
|
||||
- updated POSIX makefile to generate revision number for testing
|
||||
releases;
|
||||
- updated Linux installer build script to work with git;
|
||||
- adjusted function "check for updates" in web-interface;
|
||||
- update-scripts for Linux installer and Windows setup fetch new
|
||||
versions from GitHub releases area;
|
||||
- cleaned up project root directory, removed many unneeded files which
|
||||
were required by GNU build tools;
|
||||
- added verification for setup authenticity during update on Linux and Windows;
|
||||
- improvements in Linux installer:
|
||||
- improved compatibility with android;
|
||||
- added support for paths with spaces in parameter "--destdir";
|
||||
- auto-selecting "armhf"-architecture on ARM 64 bit systems (aarch64);
|
||||
- ignored nzbs are now added to history and processed by queue scripts:
|
||||
- when an nzb-file isn’t added to queue, for some reason, the file is now
|
||||
also added to history (in addition to messages printed to log);
|
||||
- on one hand that makes it easier to see errors (as history items instead
|
||||
of error log messages), on the other hand that provides more info for
|
||||
extension scripts and third-party programs;
|
||||
- for malformed nzb-files which cannot be parsed the status in history is
|
||||
"DELETE: SCAN";
|
||||
- for duplicate files with exactly same content status "DELETE: COPY";
|
||||
- for duplicate files having history items with status "GOOD" and for
|
||||
duplicate files having hidden history items with status "SUCCESS" which
|
||||
do not have any visible duplicates - status "DELETE: GOOD";
|
||||
- new values for field "DeleteStatus" of API-Method "history": "GOOD",
|
||||
"COPY", "SCAN";
|
||||
- new values for field "Status" of API-Method "history": "FAILURE/SCAN",
|
||||
"DELETED/COPY", "DELETED/GOOD" (they will also be passed to pp-scripts
|
||||
as NZBPP_STATUS);
|
||||
- new queue-script event "NZB_DELETED";
|
||||
- new queue event "URL_COMPLETED", with possible details: "FAILURE",
|
||||
"SCAN_SKIPPED", "SCAN_FAILURE";
|
||||
- improved quick filter (the search box at the top of the page):
|
||||
- now supporting OR, AND, NOT, parenthesis;
|
||||
- can search in specific fields;
|
||||
- can search in API-fields which are not shown in the table;
|
||||
- filters can be saved and loaded using new drop down menu;
|
||||
- new advanced duplicate par-check mode:
|
||||
- can be activated via option "ParScan=Dupe";
|
||||
- the new mode is based on the fact that the same releases are posted to
|
||||
Usenet multiple times;
|
||||
- when an item requires repair but doesn't have enough par-blocks the
|
||||
par-checker can reuse parts downloaded in other duplicates of the same
|
||||
title;
|
||||
- that makes it sometimes possible to repair downloads which would fail
|
||||
if tried to repair individually;
|
||||
- the downloads must be identifiable as duplicates (same nzb name or same
|
||||
duplicate key);
|
||||
- when par-checker needs more par-blocks it goes through the history and
|
||||
scans the download directories of duplicates until it finds missing
|
||||
blocks; it's smart enough to abort the scanning if the files come from
|
||||
different releases (based on video file size);
|
||||
- adjusted option "HealthCheck"; when set to "Delete" and duplicate
|
||||
par-scan is active, the download is aborted only if the completion is
|
||||
below 10%; that's to avoid deletion of damaged downloads which can be
|
||||
potentially repaired using new par-check mode;
|
||||
- downloads which were repaired using the new mode or which have donated
|
||||
their blocks to repair other downloads are distinguishable in the
|
||||
history details dialog with new status "EXPAR: RECIPIENT" or "EXPAR:
|
||||
DONOR"; a tooltip on the status shows amount of par-blocks
|
||||
received/donated;
|
||||
- new field "ExParStatus" returned by API-method "history";
|
||||
- to quickly find related history items use quick filter
|
||||
"-exparstatus:none" in history list;
|
||||
- when adding nzb-files via web-interface it's now possible to change name and
|
||||
other properties:
|
||||
- nzb-files in the upload-list are now clickable;
|
||||
- the click opens properties dialog where the name, password, duplicate
|
||||
key and duplicate score can be changed;
|
||||
- queue script activity is now indicated in web-interface:
|
||||
- items in download list may have new statuses "QS-QUEUED" (gray) or
|
||||
"QUEUE-SCRIPT" (green);
|
||||
- new values for field "Status" in API-method "listgroups": "QS_QUEUED",
|
||||
"QS_EXECUTING";
|
||||
- the number of active (and queued) scripts are shown in the status dialog
|
||||
in web-interface; this new row is hidden if no scripts are queued;
|
||||
- active queue scripts account for activity indicator in web-interface
|
||||
(rotating button);
|
||||
- new field "QueueScriptCount" in API-method "status" indicates number of
|
||||
queue-scripts queued for execution including the currently running;
|
||||
- added scripting support to RSS Feeds:
|
||||
- new option "FeedScript" to set global rss feed scripts;
|
||||
- new option "FeedX.FeedScript" to define per rss feed scripts;
|
||||
- new option "FeedX.Backlog" to control the RSS feed behavior when fetched for
|
||||
the first time or after changing of URL or filter;
|
||||
- implemented cleanup for field "description" in RSS feeds to remove HTML
|
||||
formatting;
|
||||
- new option "FlushQueue" ("yes" by default) to force disk cache flush when
|
||||
saving queue;
|
||||
- quick toggle of speed limit; "Command/Control + click-on-speed-icon" toggles
|
||||
between "all servers active and speed-limit=none" and "servers and speed
|
||||
limit as in the config file";
|
||||
- new option "RequiredDir" to wait for external drives mount on boot;
|
||||
- hidden webui option "rowSelect" now works for feed view too;
|
||||
- improved error message for password protected archives;
|
||||
- increased limit for log-entries in history dialog from 1000 to 10000;
|
||||
- completion tab of download details dialog (and history details dialog)
|
||||
shows per server article completion in percents; now there are also
|
||||
tooltips to show article counts;
|
||||
- do not reporting bad blocks for missing files (which are already reported as
|
||||
missing);
|
||||
- new setting to set tray icon behavior on Windows;
|
||||
- improvements in built-in web-server to fix communication errors which may
|
||||
occur on certain systems, in particular on OS X Safari:
|
||||
- implemented graceful disconnect strategy in web-server;
|
||||
- added authorization via X-Auth-Token to overcome Safari’s bug, where
|
||||
it may stop sending HTTP Basic Auth header when executing ajax
|
||||
requests;
|
||||
- pp-script EMail.py now supports new TLS/SSL mode "Force". When active the
|
||||
secure communication with SMTP server is built using secure socket on
|
||||
connection level instead of plain connection and following switch into
|
||||
secure mode using SMTP-command "STARTLS". This new mode is in particular
|
||||
required when using GMail on port 465;
|
||||
- speed improvement in built-in web-server on Windows when serving API
|
||||
requests (web-interface) for very large queue or history (with thousands
|
||||
items);
|
||||
- better performance when deleting many items from queue at once (hundreds or
|
||||
thousands);
|
||||
- improved performance in web-interface when working with very large queue or
|
||||
history (thousands of items);
|
||||
- integrated unit testing framework, created few first unit tests:
|
||||
- new configure parameter "--enable-tests" to build the program with
|
||||
tests;
|
||||
- use "nzbget --tests" to execute all tests or "nzbget --tests -h" for
|
||||
more options;
|
||||
- fixes in Linux installer:
|
||||
- fixed: installer may show wrong IP-address in the quick help message
|
||||
printed after installation (thanks to @sanderjo);
|
||||
- fixed: installation failed on certain WD NAS models due to Busybox
|
||||
limitations;
|
||||
- fixed: not working endianness detection on mipseb architecture;
|
||||
- fixed: unrar too slow on x86_64 architecture;
|
||||
- fixed: reporting of bad blocks for empty files could print garbage file
|
||||
names;
|
||||
- fixed: par-check did not work on UNC paths (Windows only);
|
||||
- fixed: config error messages were not printed to log or screen but only to
|
||||
stdout, where users typically don’t see them;
|
||||
- fixed: incorrect reading of UrlStatus from disk state;
|
||||
- fixed: total articles wasn't reset when downloading again;
|
||||
- fixed: crash on reload if a queue-script is running;
|
||||
- fixed: mark as bad may return items to queue if used on multiple items
|
||||
simultaneously;
|
||||
- fixed: updating may fail if NZBGet was not installed on the system
|
||||
drive (Windows only);
|
||||
- fixed: the program may hang during multithreading par-repair, only certain
|
||||
Linux system affected;
|
||||
- fixed: active URL download could not be deleted;
|
||||
- fixed: par-verification of repaired files were sometimes not skipped in
|
||||
quick verification mode (option "ParQuick=yes");
|
||||
- fixed: when parsing nzb-files filenames may be incorrectly detected causing
|
||||
certain downloads to fail;
|
||||
- fixed: nzb-files containing very large individual files (many GB) may cause
|
||||
program to crash or print error "Could not create file ...".
|
||||
|
||||
nzbget-15.0:
|
||||
- improved application for Windows:
|
||||
- added tray icon (near clock);
|
||||
- left click on icon pauses/resumes download;
|
||||
- right click opens menu with important functions;
|
||||
- console window can be shown/hidden via preferences (is hidden by
|
||||
default);
|
||||
- new preference to automatically start the program after login;
|
||||
- new preference to show browser on start;
|
||||
- new preference to hide tray icon;
|
||||
- menu commands to show important folders in windows explorer
|
||||
(destination, etc.);
|
||||
- on first start the config file is now placed into subdirectory
|
||||
"NZBGet" inside standard AppData-directory;
|
||||
- default destination and other directories are now placed in the
|
||||
AppData\NZBGet-directory instead of programs directory; this allows
|
||||
to install the program into "program files"-directory since the
|
||||
program does not write into the programs directory anymore;
|
||||
- the program exe has an icon now;
|
||||
- if the exe is started from windows explorer the program starts in
|
||||
application mode; if the exe is called from command prompt the program
|
||||
works in console mode;
|
||||
- added built-in update feature to windows package; accessible via
|
||||
web-interface -> settings -> system -> check for updates;
|
||||
- created installer for windows:
|
||||
- the program is installed into "program files" by default;
|
||||
- the working directory with all subdirectories is now placed into
|
||||
"AppData" directory;
|
||||
- the batch files nzbget-start.bat and nzbget-recovery-mode.bat are not
|
||||
needed and not installed anymore;
|
||||
- created installer for Linux:
|
||||
- included are precompiled binaries for the most common CPU architectures:
|
||||
x86, ARM, MIPS and PowerPC;
|
||||
- installer automatically detects CPU architecture of the system and
|
||||
installs an appropriate executable;
|
||||
- configuration file is automatically preconfigured for immediate use;
|
||||
- installation on supported platforms has become as simple as:
|
||||
download, run installer, start installed nzbget;
|
||||
- installer supports automatic updates via web-interface -> settings
|
||||
-> system - check for updates;
|
||||
- added support for password list file:
|
||||
- new option "UnpackPassFile" to set the location of the file;
|
||||
- during unpack the passwords are tried from the file until unpack
|
||||
succeeds or all passwords were tried;
|
||||
- implemented different strategies for rar4 and rar5-archives taking
|
||||
into account the features of formats;
|
||||
- for rar5-archives a wrong password is reported by unrar unambiguously
|
||||
and the program can immediately try other passwords from the password
|
||||
list;
|
||||
- for rar4-archives and for 7z-archives it is not possible to
|
||||
differentiate between damaged archive and wrong password; for those
|
||||
archives if the first unpack attempt (without password) fails the
|
||||
program executes par-check (preferably quick par-check if enabled via
|
||||
option "ParQuick) before trying the passwords from the list;
|
||||
- another optimization is that the password list is tried only when the
|
||||
first unpack attempt (without password) reports a password error or
|
||||
decryption errors; this saves unnecessary unpack attempts for damaged
|
||||
unencrypted archives;
|
||||
- options "UnrarCmd" and "SevenZipCmd" can include extra switches to pass to
|
||||
unrar/7-zip:
|
||||
- this allows for easy passing of additional parameters without creating
|
||||
of proxy shell scripts;
|
||||
- improved news server connections handling:
|
||||
- if a download of an article fails due to connection error the news
|
||||
server becomes temporary disabled (blocked) for several seconds
|
||||
(defined by option "RetryInterval");
|
||||
- the download is then retried on another news server (of the same
|
||||
level) if available;
|
||||
- if no other news servers (of the same level) exist the program will
|
||||
retry the same news server after its block interval expires;
|
||||
- this increases failure tolerance when multiple news servers are used;
|
||||
- added on-demand queue sorting:
|
||||
- one click on column title in web interface sorts the selected or all
|
||||
items;
|
||||
- if the items were already sorted in that order they are sorted
|
||||
backwards; in other words the second click sorts in descending order;
|
||||
- when sorting selected items they are also grouped together in a case
|
||||
there were holes between selected items;
|
||||
- RPC-method "editqueue" has new command "GroupSort", parameter "Text"
|
||||
must be one of: "name", "priority", "category", "size", "left"; add
|
||||
character "+" or "-" to explicitly define ascending or descending
|
||||
order (for example "name-"); if none of these characters
|
||||
is used the auto-mode is active: the items are sorted in ascending
|
||||
order first, if nothing changed - they are sorted again in descending
|
||||
order;
|
||||
- added restricted user and add-user:
|
||||
- restricted user has access to most program functions but cannot see
|
||||
security related options (including usernames and passwords) and
|
||||
cannot save configuration;
|
||||
- restricted user can be used with other programs and web-sites;
|
||||
- add-user can only add new downloads via RPC-API and can be used with
|
||||
other programs or web-sites;
|
||||
- added per-nzb logging:
|
||||
- each nzb now has its own individual log;
|
||||
- messages printed during download or post-processing are saved;
|
||||
- the messages can be retrieved later at any time;
|
||||
- new button "Log" in the history details dialog;
|
||||
- button "Log" in the download details dialog is now active during
|
||||
download too (not only during post-processing);
|
||||
- the log contains all nzb-related messages except detail-messages and
|
||||
errors printed during retrieving of articles (they would produce way
|
||||
too many messages and are not that useful anyway);
|
||||
- new option "NzbLog" to deactivate per-nzb logging if necessary;
|
||||
- per-nzb logs are saved in the queue-directory (option "QueueDir");
|
||||
- new RPC-method "loadlog" returns the previously saved messages for a
|
||||
given nzb-file;
|
||||
- new field "MessageCount" is returned by RPC-methods "listgroups" and
|
||||
"history" and indicates if there are any messages saved for the item;
|
||||
- parameter "NumberOfLogEntries" of RPC-method "listgroups" and the
|
||||
field "Log" returned by the method are now deprecated, use method
|
||||
"loadlog" instead;
|
||||
- field "PostInfoText" returned by RPC-method "listgroups" is now
|
||||
automatically filled with the latest message printed by a pp-script
|
||||
eliminating the need to access deprecated field "Log"';
|
||||
- actions for history items can now be performed for multiple (selected) records:
|
||||
- post-process again, download again, mark as good, mark as bad;
|
||||
- extended RPC-API method "editqueue": for history-records of type
|
||||
"URL" the action "HistoryRedownload" can now be used as synonym to
|
||||
"HistoryReturn" (makes it easier to redownload multiple items of
|
||||
different types (URL and NZB) with one API call).
|
||||
- options "ParIgnoreExt" and "ExtCleanupDisk" can now contain wildcard
|
||||
characters * and ?;
|
||||
- added new option "ServerX.Retention" to define server retention time (days);
|
||||
files older than configured server retention time are not even tried on this
|
||||
server;
|
||||
- added support for negative numeric values in rss filter (useful for fields
|
||||
"dupescore" and "priority");
|
||||
- added subcommand "HA" to remote command "--list/-L" to list the whole
|
||||
history including hidden records;
|
||||
- added optional parameters to remote command "--append/-A" allowing to pass
|
||||
duplicate key, duplicate mode and duplicate score; removed parameters "F"
|
||||
and "U" of command "--append/-A", which were used to set mode (file or URL),
|
||||
which is now detected automatically; the parameters are still supported for
|
||||
compatibility;
|
||||
- name and category of history items can now be changed in web-interface;
|
||||
RPC-API method "editqueue" extended with new actions "HistorySetName" and
|
||||
"HistorySetCategory";
|
||||
- improved timeout handling during establishing of connections;
|
||||
- updated pp-script "EMail.py":
|
||||
- using the new nzb-log feature;
|
||||
- new option "SendMail" allows to choose if the e-mail should be send
|
||||
always or on failure only;
|
||||
- updated pp-script "Logger.py" to use the new nzb-log feature;
|
||||
- improved cleanup (option ExtCleanupDisk):
|
||||
- if download was successful with health 100% the cleanup is now
|
||||
performed even if par-check and unpack were not made; previously a
|
||||
successful par-check or unpack were required for cleanup;
|
||||
- now the files are deleted in subdirectories too (recursively);
|
||||
- added a small button near feed name in the feed menu on downloads-page; a
|
||||
click on the button fetches the feed, whereas a click on the feed title
|
||||
shows feed's content (as before);
|
||||
- improved detection of malformed nzb-files: nzbs which are valid
|
||||
xml-documents but without nzb content are now rejected with an appropriate
|
||||
error message;
|
||||
- new action "Mark as success" on history page and in history details dialog:
|
||||
- items marked as success are considered successfully downloaded and
|
||||
processed, which is important for duplicate check;
|
||||
- use this command if the download was repaired outside of NZBGet;
|
||||
- new action "HistoryMarkSuccess" in RPC-method "editqueue";
|
||||
- new subcommand "S" of command "-E H" (command line interface);
|
||||
- new status "SUCCESS/MARK" can be returned by RPC-method "history";
|
||||
- improved support for update-scripts:
|
||||
- all command line parameters used to launch nzbget are passed to the
|
||||
script in env vars NZBUP_CMDLINEX, where X is a parameter number
|
||||
starting with 0;
|
||||
- if the path to update-script defined in package-info.json does not
|
||||
start with slash the path is considered being relative to application
|
||||
directory;
|
||||
- new env var NZBUP_RUNMODE (DAEMON, SERVER) is passed to the script;
|
||||
- fixed: env var NZBUP_PROCESSID had wrong value (ID of the parent
|
||||
process instead of the nzbget process);
|
||||
- added button "Test Connection" to make a news server connection test from
|
||||
web-interface;
|
||||
- renamed option "CreateBrokenLog" to "BrokenLog"; the old option name is
|
||||
recognized and automatically converted when the configuration is saved in
|
||||
web-interface;
|
||||
- improved the quality of speed throttling when a speed limit is active;
|
||||
- added hidden webui setting "rowSelect" to select records by clicking on any
|
||||
part of the row, not just on the check mark; to activate it change the
|
||||
setting "rowSelect" in webui/index.js;
|
||||
- when moving files to final destination the hidden files (with names starting
|
||||
with dot) are considered unimportant and no errors are printed if they
|
||||
cannot be moved; such files (.AppleDouble, .DS_Store, etc.) are usually
|
||||
used by services to hold metadata and can be safely ignored;
|
||||
- option sets (such as news-servers, categories, etc.) can now be reordered
|
||||
using news buttons "move up" and "move down";
|
||||
- updated info in about dialogs (Windows and Mac OS X);
|
||||
- updated description of few options;
|
||||
- changed defaults for few logging options;
|
||||
- improved timeout handling when connecting to news servers which have
|
||||
multiple addresses;
|
||||
- improved error handling when communicating with secure servers (do not
|
||||
trying to send quit-command if connection could not be established or was
|
||||
interrupted; this avoids unnecessary timeout);
|
||||
- improved connection handling when fetching nzb-files and rss feeds; do not
|
||||
print warning "Content-Length is not submitted by server..." anymore;
|
||||
- download speed in context menu of menubar icon is now shown in MB/s instead
|
||||
of KB/s (for speeds from 1 MB/s) (Mac OS X only);
|
||||
- configuration file nzbget.conf is now also searched in the app-directory on
|
||||
all platforms (for easier installation);
|
||||
- removed shell script "nzbgetd" which were used to control nzbget as a
|
||||
service; modern systems manage services in a diffreent way and do not
|
||||
require that old script anymore;
|
||||
- disabled changing of compiler options during configuring in debug mode
|
||||
(--enable-debug); it conflicted with cross-compiling and did not allow to
|
||||
pass extra options via CXXFLAGS; required debug options must be passed via
|
||||
CXXFLAGS now (for example for gcc: CXXFLAGS=-g ./configure --enable-debug);
|
||||
- disabled unnecessary assert-statements in par2-module when building in
|
||||
release mode;
|
||||
- fixed: parsing of RPC-parameters passed via URL were sometimes incorrect;
|
||||
- fixed: lowercase hex digits were not correctly parsed in URLs passed to
|
||||
RPC-API method "append";
|
||||
- fixed: in JSON-RPC the request-id was not transfered back in the response as
|
||||
required by JSON-RPC specification;
|
||||
- fixed: par-check in full verification mode (not in quick mode) could not
|
||||
detect damaged files if they were completely empty (0 bytes), which is
|
||||
possible when option "DirectWrite" was not active and all articles of the
|
||||
file were missing;
|
||||
- fixed possible crash when using remote command "-B dump" to print debug
|
||||
info;
|
||||
- fixed: remote command "-L HA" (which prints the history including hidden
|
||||
records) could crash;
|
||||
- suppress printing of memory leaks reports when the program terminates
|
||||
because of wrong command line switches (Windows debug mode only);
|
||||
- fixed: command "nzbget -L H" may crash if the history contained URL-items
|
||||
with certain status;
|
||||
- fixed: action "Split" may not work for bad nzb-files with missing segments;
|
||||
new Field "Progress" returned by RPC-method "listfiles" shows the download
|
||||
progress of the file taking missing articles into account;
|
||||
- if the lock-file cannot be created or the lock could not be acquired an
|
||||
error message is printed to the log-file;
|
||||
- fixed: update log shown during automatic update via web-interface may show
|
||||
duplicate messages or messages may clear out;
|
||||
- fixed: web-interface may fail to load on Firefox mobile;
|
||||
- fixed: command "make install" installed README from par2-subdirectory
|
||||
instead of main README.
|
||||
|
||||
nzbget-14.2:
|
||||
- fixed: the program could crash during download when article cache was
|
||||
active (more likely on very high download speeds);
|
||||
- fixed: the program could crash during download when article cache was active
|
||||
(more likely on very high download speeds);
|
||||
- fixed: unlike to all other scripts the update-script should not be
|
||||
automatically terminated when the program quits;
|
||||
- fixed: XML-RPC method "history" returned invalid xml when used with
|
||||
parameter "hidden=true" (JSON-RPC was fine).
|
||||
|
||||
nzbget-14.1:
|
||||
- fixed: program could crash during unpack (Posix) or unpack failure
|
||||
was reported (Windows);
|
||||
- fixed: program could crash during unpack (Posix) or unpack failure was
|
||||
reported (Windows);
|
||||
- fixed: quick par-check could hang on certain nzb-files containing multiple
|
||||
par-sets (occured only in 64 bit mode);
|
||||
- fixed: menubar icon was not visible on OSX in dark mode;
|
||||
- system sleep on idle state is now prevented during download and
|
||||
post-processing (Mac OSX only);
|
||||
- fixed: unrar may sometimes fail with message "no files to extract"
|
||||
(certain Linux systems);
|
||||
- fixed: menubar icon was not visible on Mac OS X in dark mode; system sleep
|
||||
on idle state is now prevented during download and post-processing
|
||||
(Mac OS X only);
|
||||
- fixed: unrar may sometimes fail with message "no files to extract" (certain
|
||||
Linux systems);
|
||||
- fixed false memory leak warning when compiled in debug mode (Windows only);
|
||||
- fixed: Mac OS X app didn't work on OS X 10.7 Lion.
|
||||
|
||||
nzbget-14.0:
|
||||
- added article cache:
|
||||
@@ -223,7 +750,7 @@ nzbget-14.0:
|
||||
- fixed: splitted .cbr-files were not properly joined;
|
||||
- fixed: inner files (files listed in nzb) bigger than 2GB could not be
|
||||
downloaded;
|
||||
- fixed: cleanup may leave some files undeleted (Mac OSX only);
|
||||
- fixed: cleanup may leave some files undeleted (Mac OS X only);
|
||||
- fixed: compiler error if configured using parameter "--disable-gzip";
|
||||
- fixed: one log-message was printed only to global log but not to nzb-item
|
||||
pp-log;
|
||||
@@ -677,7 +1204,7 @@ nzbget-12.0:
|
||||
be viewed and changed in download-edit-dialog and
|
||||
history-edit-dialog via new button "Dupe";
|
||||
- for full documentation see http://nzbget.net/RSS#Duplicates;
|
||||
- created NZBGet.app - NZBGet is now a user friendly Mac OSX application
|
||||
- created NZBGet.app - NZBGet is now a user friendly Mac OS X application
|
||||
with easy installation and seamless integration into OS UI:
|
||||
works in background, is controlled from a web-browser, few
|
||||
important functions are accessible via menubar icon;
|
||||
@@ -811,7 +1338,7 @@ nzbget-12.0:
|
||||
DestDir is mounted to a network drive which is not available on program start;
|
||||
- added special handling for files ".AppleDouble" and ".DS_Store" during
|
||||
unpack to avoid problems on NAS having support for AFP protocol (used
|
||||
on Mac OSX);
|
||||
on Mac OS X);
|
||||
- history records with failed script status are now shown as "PP-FAILURE"
|
||||
in history list (instead of just "FAILURE");
|
||||
- option "DiskSpace" now checks space on "InterDir" in addition to
|
||||
@@ -2034,10 +2561,10 @@ nzbget-0.3.0:
|
||||
install/remove nzbget-Service. Servers and clients can run on diferrent
|
||||
operating systems;
|
||||
- Improved compatibility with POSIX systems; Tested on:
|
||||
- Linux Debian 3.1 on x86;
|
||||
- Linux BusyBox with uClibc on MIPSEL;
|
||||
- PC-BSD 1.4 (based on FreeBSD 6.2) on x86;
|
||||
- Solaris 10 on x86;
|
||||
- Linux Debian 3.1 on x86;
|
||||
- Linux BusyBox with uClibc on MIPSEL;
|
||||
- PC-BSD 1.4 (based on FreeBSD 6.2) on x86;
|
||||
- Solaris 10 on x86;
|
||||
- Many memory-leaks and thread issues were fixed;
|
||||
- The program was thoroughly worked over. Almost every line of code was
|
||||
revised.
|
||||
|
||||
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.
|
||||
|
||||
206
Makefile.am
206
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-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,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,24 @@ 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/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,6 +56,10 @@ 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 \
|
||||
@@ -62,20 +78,24 @@ 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/Unpack.cpp \
|
||||
@@ -88,14 +108,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 +131,22 @@ 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
|
||||
code_revision.cpp
|
||||
|
||||
if WITH_PAR2
|
||||
nzbget_SOURCES += \
|
||||
@@ -171,6 +196,7 @@ endif
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-I$(srcdir)/daemon/connect \
|
||||
-I$(srcdir)/daemon/extension \
|
||||
-I$(srcdir)/daemon/feed \
|
||||
-I$(srcdir)/daemon/frontend \
|
||||
-I$(srcdir)/daemon/main \
|
||||
@@ -181,19 +207,57 @@ AM_CPPFLAGS = \
|
||||
-I$(srcdir)/daemon/util \
|
||||
-I$(srcdir)/lib/par2
|
||||
|
||||
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/ParCheckerTest.cpp \
|
||||
tests/postprocess/ParRenamerTest.cpp \
|
||||
tests/postprocess/DupeMatcherTest.cpp \
|
||||
tests/queue/NzbFileTest.cpp \
|
||||
tests/nntp/ServerPoolTest.cpp \
|
||||
tests/util/FileSystemTest.cpp \
|
||||
tests/util/NStringTest.cpp \
|
||||
tests/util/UtilTest.cpp
|
||||
|
||||
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/setup/nzbget-setup.nsi \
|
||||
windows/setup/install.bmp \
|
||||
windows/setup/uninstall.bmp
|
||||
|
||||
osx_FILES = \
|
||||
osx/App_Prefix.pch \
|
||||
@@ -226,10 +290,21 @@ 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-freebsd
|
||||
|
||||
doc_FILES = \
|
||||
README \
|
||||
ChangeLog \
|
||||
COPYING \
|
||||
COPYING
|
||||
|
||||
par2doc_FILES = \
|
||||
lib/par2/AUTHORS \
|
||||
lib/par2/README
|
||||
|
||||
@@ -272,8 +347,24 @@ 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
|
||||
|
||||
# Install
|
||||
sbin_SCRIPTS = nzbgetd
|
||||
dist_doc_DATA = $(doc_FILES)
|
||||
exampleconfdir = $(datadir)/nzbget
|
||||
dist_exampleconf_DATA = $(exampleconf_FILES)
|
||||
@@ -291,13 +382,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"
|
||||
@@ -320,44 +404,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="$(shell git branch | sed -n -e 's/^\* \(.*\)/\1/p')"; \
|
||||
M="$(shell git status --porcelain)" ; \
|
||||
if test "$$M" != "" ; then \
|
||||
M="M" ; \
|
||||
fi ; \
|
||||
if test "$$B" = "master" ; then \
|
||||
V="$$M" ; \
|
||||
elif test "$$B" = "develop" ; then \
|
||||
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
|
||||
V="$${V}$$M" ; \
|
||||
else \
|
||||
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
|
||||
V="$${V}$$M ($$B)" ; \
|
||||
fi ; \
|
||||
H="$(shell 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 '{}' ';'
|
||||
@@ -369,4 +468,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
|
||||
|
||||
978
Makefile.in
vendored
978
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>
|
||||
|
||||
18
README.md
Normal file
18
README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# NZBGet #
|
||||
[](http://www.gnu.org/licenses/)
|
||||
[](https://travis-ci.org/nzbget/nzbget)
|
||||
|
||||
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 extraordinary performance and efficiency.
|
||||
|
||||
NZBGet can be run at almost every platform - classic PCs, NAS, media players, SAT-receivers, WLAN-routers, etc.
|
||||
The download area provides precompiled binaries
|
||||
for Windows, Mac OS X and Linux (compatible with many CPUs and platform variants). For other platforms
|
||||
the program can be compiled from sources.
|
||||
|
||||
- [Home page (nzbget.net)](http://nzbget.net) - for first time visitors, learn more about NZBGet;
|
||||
- [Downloads](http://nzbget.net/download) - get the binaries and sources;
|
||||
- [Documentation](https://github.com/nzbget/nzbget/wiki) - installation manuals, HOW-TOs, API;
|
||||
- [Forum](http://forum.nzbget.net) - get support, share your ideas, scripts, add-ons.
|
||||
346
aclocal.m4
vendored
346
aclocal.m4
vendored
@@ -11,164 +11,6 @@
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
|
||||
#
|
||||
# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
|
||||
#
|
||||
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# PKG_PROG_PKG_CONFIG([MIN-VERSION])
|
||||
# ----------------------------------
|
||||
AC_DEFUN([PKG_PROG_PKG_CONFIG],
|
||||
[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
|
||||
m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
|
||||
AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
|
||||
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
|
||||
AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
|
||||
fi
|
||||
if test -n "$PKG_CONFIG"; then
|
||||
_pkg_min_version=m4_default([$1], [0.9.0])
|
||||
AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
|
||||
if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
PKG_CONFIG=""
|
||||
fi
|
||||
|
||||
fi[]dnl
|
||||
])# PKG_PROG_PKG_CONFIG
|
||||
|
||||
# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
#
|
||||
# Check to see whether a particular set of modules exists. Similar
|
||||
# to PKG_CHECK_MODULES(), but does not set variables or print errors.
|
||||
#
|
||||
#
|
||||
# Similar to PKG_CHECK_MODULES, make sure that the first instance of
|
||||
# this or PKG_CHECK_MODULES is called, or make sure to call
|
||||
# PKG_CHECK_EXISTS manually
|
||||
# --------------------------------------------------------------
|
||||
AC_DEFUN([PKG_CHECK_EXISTS],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
|
||||
m4_ifval([$2], [$2], [:])
|
||||
m4_ifvaln([$3], [else
|
||||
$3])dnl
|
||||
fi])
|
||||
|
||||
|
||||
# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
|
||||
# ---------------------------------------------
|
||||
m4_define([_PKG_CONFIG],
|
||||
[if test -n "$PKG_CONFIG"; then
|
||||
if test -n "$$1"; then
|
||||
pkg_cv_[]$1="$$1"
|
||||
else
|
||||
PKG_CHECK_EXISTS([$3],
|
||||
[pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
|
||||
[pkg_failed=yes])
|
||||
fi
|
||||
else
|
||||
pkg_failed=untried
|
||||
fi[]dnl
|
||||
])# _PKG_CONFIG
|
||||
|
||||
# _PKG_SHORT_ERRORS_SUPPORTED
|
||||
# -----------------------------
|
||||
AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
|
||||
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
|
||||
_pkg_short_errors_supported=yes
|
||||
else
|
||||
_pkg_short_errors_supported=no
|
||||
fi[]dnl
|
||||
])# _PKG_SHORT_ERRORS_SUPPORTED
|
||||
|
||||
|
||||
# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
|
||||
# [ACTION-IF-NOT-FOUND])
|
||||
#
|
||||
#
|
||||
# Note that if there is a possibility the first call to
|
||||
# PKG_CHECK_MODULES might not happen, you should be sure to include an
|
||||
# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
|
||||
#
|
||||
#
|
||||
# --------------------------------------------------------------
|
||||
AC_DEFUN([PKG_CHECK_MODULES],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
|
||||
AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
|
||||
AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
|
||||
|
||||
pkg_failed=no
|
||||
AC_MSG_CHECKING([for $1])
|
||||
|
||||
_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
|
||||
_PKG_CONFIG([$1][_LIBS], [libs], [$2])
|
||||
|
||||
m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
|
||||
and $1[]_LIBS to avoid the need to call pkg-config.
|
||||
See the pkg-config man page for more details.])
|
||||
|
||||
if test $pkg_failed = yes; then
|
||||
_PKG_SHORT_ERRORS_SUPPORTED
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
$1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
|
||||
else
|
||||
$1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
|
||||
|
||||
ifelse([$4], , [AC_MSG_ERROR(dnl
|
||||
[Package requirements ($2) were not met:
|
||||
|
||||
$$1_PKG_ERRORS
|
||||
|
||||
Consider adjusting the PKG_CONFIG_PATH environment variable if you
|
||||
installed software in a non-standard prefix.
|
||||
|
||||
_PKG_TEXT
|
||||
])],
|
||||
[AC_MSG_RESULT([no])
|
||||
$4])
|
||||
elif test $pkg_failed = untried; then
|
||||
ifelse([$4], , [AC_MSG_FAILURE(dnl
|
||||
[The pkg-config script could not be found or is too old. Make sure it
|
||||
is in your PATH or set the PKG_CONFIG environment variable to the full
|
||||
path to pkg-config.
|
||||
|
||||
_PKG_TEXT
|
||||
|
||||
To get pkg-config, see <http://pkg-config.freedesktop.org/>.])],
|
||||
[$4])
|
||||
else
|
||||
$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
|
||||
$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
|
||||
AC_MSG_RESULT([yes])
|
||||
ifelse([$3], , :, [$3])
|
||||
fi[]dnl
|
||||
])# PKG_CHECK_MODULES
|
||||
|
||||
# Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
@@ -647,6 +489,35 @@ fi
|
||||
rmdir .tst 2>/dev/null
|
||||
AC_SUBST([am__leading_dot])])
|
||||
|
||||
# Add --enable-maintainer-mode option to configure. -*- Autoconf -*-
|
||||
# From Jim Meyering
|
||||
|
||||
# Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003, 2004, 2005
|
||||
# Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# serial 4
|
||||
|
||||
AC_DEFUN([AM_MAINTAINER_MODE],
|
||||
[AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles])
|
||||
dnl maintainer-mode is disabled by default
|
||||
AC_ARG_ENABLE(maintainer-mode,
|
||||
[ --enable-maintainer-mode enable make rules and dependencies not useful
|
||||
(and sometimes confusing) to the casual installer],
|
||||
USE_MAINTAINER_MODE=$enableval,
|
||||
USE_MAINTAINER_MODE=no)
|
||||
AC_MSG_RESULT([$USE_MAINTAINER_MODE])
|
||||
AM_CONDITIONAL(MAINTAINER_MODE, [test $USE_MAINTAINER_MODE = yes])
|
||||
MAINT=$MAINTAINER_MODE_TRUE
|
||||
AC_SUBST(MAINT)dnl
|
||||
]
|
||||
)
|
||||
|
||||
AU_DEFUN([jm_MAINTAINER_MODE], [AM_MAINTAINER_MODE])
|
||||
|
||||
# Check to see how 'make' treats includes. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc.
|
||||
@@ -830,6 +701,165 @@ AC_DEFUN([_AM_SET_OPTIONS],
|
||||
AC_DEFUN([_AM_IF_OPTION],
|
||||
[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
|
||||
|
||||
# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
|
||||
#
|
||||
# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
|
||||
#
|
||||
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# PKG_PROG_PKG_CONFIG([MIN-VERSION])
|
||||
# ----------------------------------
|
||||
AC_DEFUN([PKG_PROG_PKG_CONFIG],
|
||||
[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
|
||||
m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
|
||||
AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
|
||||
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
|
||||
AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
|
||||
fi
|
||||
if test -n "$PKG_CONFIG"; then
|
||||
_pkg_min_version=m4_default([$1], [0.9.0])
|
||||
AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
|
||||
if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
PKG_CONFIG=""
|
||||
fi
|
||||
|
||||
fi[]dnl
|
||||
])# PKG_PROG_PKG_CONFIG
|
||||
|
||||
# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
#
|
||||
# Check to see whether a particular set of modules exists. Similar
|
||||
# to PKG_CHECK_MODULES(), but does not set variables or print errors.
|
||||
#
|
||||
#
|
||||
# Similar to PKG_CHECK_MODULES, make sure that the first instance of
|
||||
# this or PKG_CHECK_MODULES is called, or make sure to call
|
||||
# PKG_CHECK_EXISTS manually
|
||||
# --------------------------------------------------------------
|
||||
AC_DEFUN([PKG_CHECK_EXISTS],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
|
||||
m4_ifval([$2], [$2], [:])
|
||||
m4_ifvaln([$3], [else
|
||||
$3])dnl
|
||||
fi])
|
||||
|
||||
|
||||
# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
|
||||
# ---------------------------------------------
|
||||
m4_define([_PKG_CONFIG],
|
||||
[if test -n "$PKG_CONFIG"; then
|
||||
if test -n "$$1"; then
|
||||
pkg_cv_[]$1="$$1"
|
||||
else
|
||||
PKG_CHECK_EXISTS([$3],
|
||||
[pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
|
||||
[pkg_failed=yes])
|
||||
fi
|
||||
else
|
||||
pkg_failed=untried
|
||||
fi[]dnl
|
||||
])# _PKG_CONFIG
|
||||
|
||||
# _PKG_SHORT_ERRORS_SUPPORTED
|
||||
# -----------------------------
|
||||
AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
|
||||
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
|
||||
_pkg_short_errors_supported=yes
|
||||
else
|
||||
_pkg_short_errors_supported=no
|
||||
fi[]dnl
|
||||
])# _PKG_SHORT_ERRORS_SUPPORTED
|
||||
|
||||
|
||||
# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
|
||||
# [ACTION-IF-NOT-FOUND])
|
||||
#
|
||||
#
|
||||
# Note that if there is a possibility the first call to
|
||||
# PKG_CHECK_MODULES might not happen, you should be sure to include an
|
||||
# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
|
||||
#
|
||||
#
|
||||
# --------------------------------------------------------------
|
||||
AC_DEFUN([PKG_CHECK_MODULES],
|
||||
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
|
||||
AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
|
||||
AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
|
||||
|
||||
pkg_failed=no
|
||||
AC_MSG_CHECKING([for $1])
|
||||
|
||||
_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
|
||||
_PKG_CONFIG([$1][_LIBS], [libs], [$2])
|
||||
|
||||
m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
|
||||
and $1[]_LIBS to avoid the need to call pkg-config.
|
||||
See the pkg-config man page for more details.])
|
||||
|
||||
if test $pkg_failed = yes; then
|
||||
_PKG_SHORT_ERRORS_SUPPORTED
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
$1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
|
||||
else
|
||||
$1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
|
||||
|
||||
ifelse([$4], , [AC_MSG_ERROR(dnl
|
||||
[Package requirements ($2) were not met:
|
||||
|
||||
$$1_PKG_ERRORS
|
||||
|
||||
Consider adjusting the PKG_CONFIG_PATH environment variable if you
|
||||
installed software in a non-standard prefix.
|
||||
|
||||
_PKG_TEXT
|
||||
])],
|
||||
[AC_MSG_RESULT([no])
|
||||
$4])
|
||||
elif test $pkg_failed = untried; then
|
||||
ifelse([$4], , [AC_MSG_FAILURE(dnl
|
||||
[The pkg-config script could not be found or is too old. Make sure it
|
||||
is in your PATH or set the PKG_CONFIG environment variable to the full
|
||||
path to pkg-config.
|
||||
|
||||
_PKG_TEXT
|
||||
|
||||
To get pkg-config, see <http://pkg-config.freedesktop.org/>.])],
|
||||
[$4])
|
||||
else
|
||||
$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
|
||||
$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
|
||||
AC_MSG_RESULT([yes])
|
||||
ifelse([$3], , :, [$3])
|
||||
fi[]dnl
|
||||
])# PKG_CHECK_MODULES
|
||||
|
||||
|
||||
# Check to make sure that the build environment is sane. -*- Autoconf -*-
|
||||
|
||||
# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005
|
||||
|
||||
56
config.h.in
56
config.h.in
@@ -19,6 +19,9 @@
|
||||
/* 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 +38,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,9 +83,6 @@
|
||||
/* 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 you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
@@ -87,9 +92,6 @@
|
||||
/* 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 OpenSSL library for TLS/SSL-support. */
|
||||
#undef HAVE_OPENSSL
|
||||
|
||||
@@ -99,27 +101,12 @@
|
||||
/* 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 +116,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 +131,8 @@
|
||||
/* 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 to exclude debug-code */
|
||||
#undef NDEBUG
|
||||
|
||||
/* Name of package */
|
||||
#undef PACKAGE
|
||||
@@ -198,14 +177,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
|
||||
|
||||
153
configure.ac
153
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-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,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.2, hugbug@users.sourceforge.net)
|
||||
AC_CANONICAL_SYSTEM
|
||||
AM_INIT_AUTOMAKE(nzbget, 14.2)
|
||||
AC_INIT(nzbget, 17.0, hugbug@users.sourceforge.net)
|
||||
AC_CONFIG_AUX_DIR(posix)
|
||||
AC_CANONICAL_TARGET
|
||||
AM_INIT_AUTOMAKE([foreign])
|
||||
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,6 +46,21 @@ 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.
|
||||
@@ -76,6 +85,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
|
||||
@@ -163,14 +180,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
|
||||
@@ -261,17 +270,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 +308,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
|
||||
@@ -311,20 +328,13 @@ 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])
|
||||
AC_CHECK_HEADERS([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 +374,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,
|
||||
@@ -392,16 +401,21 @@ if test "$USETLS" = "yes"; then
|
||||
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
|
||||
@@ -467,16 +481,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"))
|
||||
@@ -524,16 +543,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
|
||||
@@ -597,8 +606,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-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,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
|
||||
@@ -47,73 +44,105 @@ public:
|
||||
csCancelled
|
||||
};
|
||||
|
||||
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;
|
||||
Connection(const char* host, int port, bool tls);
|
||||
Connection(SOCKET socket, bool tls);
|
||||
virtual ~Connection();
|
||||
static void Init();
|
||||
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; }
|
||||
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; }
|
||||
#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;
|
||||
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_broken = false;
|
||||
bool m_gracefull = 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 herrno = 0,
|
||||
const char* herrMsg = nullptr);
|
||||
virtual void PrintError(const char* errMsg);
|
||||
bool DoConnect();
|
||||
bool DoDisconnect();
|
||||
bool InitSocketOpts();
|
||||
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();
|
||||
private:
|
||||
static void Final();
|
||||
friend class ConnectionFinalizer;
|
||||
};
|
||||
|
||||
#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
|
||||
495
daemon/connect/TlsSocket.cpp
Normal file
495
daemon/connect/TlsSocket.cpp
Normal file
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
|
||||
#ifndef DISABLE_TLS
|
||||
|
||||
#include "TlsSocket.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
|
||||
class TlsSocketFinalizer
|
||||
{
|
||||
public:
|
||||
~TlsSocketFinalizer()
|
||||
{
|
||||
TlsSocket::Final();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<TlsSocketFinalizer> m_tlsSocketFinalizer;
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* 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 uint32 openssl_thread_id(void)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return (uint32)GetCurrentThreadId();
|
||||
#else
|
||||
return (uint32)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
|
||||
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
|
||||
for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
|
||||
{
|
||||
g_OpenSSLMutexes.emplace_back(std::make_unique<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 */
|
||||
|
||||
m_tlsSocketFinalizer = std::make_unique<TlsSocketFinalizer>();
|
||||
}
|
||||
|
||||
void TlsSocket::Final()
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
gnutls_global_deinit();
|
||||
#endif /* HAVE_LIBGNUTLS */
|
||||
}
|
||||
|
||||
TlsSocket::~TlsSocket()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void TlsSocket::ReportError(const char* errMsg)
|
||||
{
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
const char* errstr = gnutls_strerror(m_retCode);
|
||||
if (m_suppressErrors)
|
||||
{
|
||||
debug("%s: %s", errMsg, errstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintError(BString<1024>("%s: %s", errMsg, 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_suppressErrors)
|
||||
{
|
||||
debug("%s: %s", errMsg, errstr);
|
||||
}
|
||||
else if (errcode != 0)
|
||||
{
|
||||
PrintError(BString<1024>("%s: %s", errMsg, errstr));
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintError(errMsg);
|
||||
}
|
||||
} 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");
|
||||
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");
|
||||
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");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_session = sess;
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
const char* priority = !m_cipher.Empty() ? m_cipher.Str() : "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");
|
||||
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 host name 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");
|
||||
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("TLS handshake failed");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_session = SSL_new((SSL_CTX*)m_context);
|
||||
if (!m_session)
|
||||
{
|
||||
ReportError("Could not create TLS session");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
|
||||
{
|
||||
ReportError("Could not select cipher for TLS");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (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, 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)
|
||||
{
|
||||
ReportError("TLS handshake failed");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connected = true;
|
||||
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
|
||||
68
daemon/connect/TlsSocket.h
Normal file
68
daemon/connect/TlsSocket.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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();
|
||||
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:
|
||||
bool m_isClient;
|
||||
CString m_host;
|
||||
CString m_certFile;
|
||||
CString m_keyFile;
|
||||
CString m_cipher;
|
||||
SOCKET m_socket;
|
||||
bool m_suppressErrors = false;
|
||||
bool m_initialized = false;
|
||||
bool m_connected = false;
|
||||
int m_retCode;
|
||||
|
||||
// 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);
|
||||
|
||||
static void Final();
|
||||
friend class TlsSocketFinalizer;
|
||||
};
|
||||
|
||||
#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-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,89 +14,38 @@
|
||||
* 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 "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 +54,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->GetRetries() > 0 ? g_Options->GetRetries() : 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_Options->GetPauseDownload()))
|
||||
{
|
||||
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
|
||||
detail("Waiting %i sec to retry", g_Options->GetRetryInterval());
|
||||
int msec = 0;
|
||||
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) &&
|
||||
!(!m_bForce && g_pOptions->GetPauseDownload()))
|
||||
while (!IsStopped() && (msec < g_Options->GetRetryInterval() * 1000) &&
|
||||
!(!m_force && g_Options->GetPauseDownload()))
|
||||
{
|
||||
usleep(100 * 1000);
|
||||
msec += 100;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsStopped() || (!m_bForce && g_pOptions->GetPauseDownload()))
|
||||
if (IsStopped() || (!m_force && g_Options->GetPauseDownload()))
|
||||
{
|
||||
Status = adRetry;
|
||||
break;
|
||||
@@ -147,24 +95,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 +114,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 +136,7 @@ WebDownloader::EStatus WebDownloader::Download()
|
||||
{
|
||||
EStatus Status = adRunning;
|
||||
|
||||
URL url(m_szURL);
|
||||
URL url(m_url);
|
||||
|
||||
Status = CreateConnection(&url);
|
||||
if (Status != adRunning)
|
||||
@@ -207,19 +144,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 +177,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 +300,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 +311,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 +331,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 +349,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 +411,83 @@ 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))
|
||||
{
|
||||
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 +503,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 +513,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 +608,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 +631,48 @@ 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;
|
||||
std::unique_ptr<Connection> connection = std::move(m_connection);
|
||||
bool terminated = Kill();
|
||||
if (terminated && pConnection)
|
||||
if (terminated && connection)
|
||||
{
|
||||
debug("Terminating connection");
|
||||
pConnection->SetSuppressErrors(true);
|
||||
pConnection->Cancel();
|
||||
pConnection->Disconnect();
|
||||
delete pConnection;
|
||||
connection->SetSuppressErrors(true);
|
||||
connection->Cancel();
|
||||
connection->Disconnect();
|
||||
connection.reset();
|
||||
}
|
||||
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,63 @@ 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);
|
||||
bool Terminate();
|
||||
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
|
||||
|
||||
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", GetInfoName());
|
||||
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
|
||||
308
daemon/extension/PostScript.cpp
Normal file
308
daemon/extension/PostScript.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* 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 "PostScript.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "Options.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);
|
||||
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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", GetInfoName());
|
||||
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", GetInfoName());
|
||||
return ScriptStatus::srFailure;
|
||||
|
||||
case POSTPROCESS_NONE:
|
||||
PrintMessage(Message::mkInfo, "%s skipped", GetInfoName());
|
||||
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", GetInfoName());
|
||||
return ScriptStatus::srFailure;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName());
|
||||
m_postInfo->SetRequestParCheck(true);
|
||||
m_postInfo->SetForceRepair(true);
|
||||
return ScriptStatus::srSuccess;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName());
|
||||
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_Options->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_Options->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
|
||||
{
|
||||
usleep(100 * 1000);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
#endif
|
||||
511
daemon/extension/QueueScript.cpp
Normal file
511
daemon/extension/QueueScript.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
/*
|
||||
* 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 "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_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)
|
||||
{
|
||||
PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
|
||||
nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
|
||||
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, 0, 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)
|
||||
{
|
||||
SetLogPrefix(nullptr);
|
||||
PrintMessage(Message::mkWarning, "Marking %s as bad", *m_nzbName);
|
||||
SetLogPrefix(m_script->GetDisplayName());
|
||||
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 queue-scripts
|
||||
const char* queueScript = g_Options->GetQueueScript();
|
||||
if (!Util::EmptyStr(queueScript))
|
||||
{
|
||||
Tokenizer tok(queueScript, ",;");
|
||||
while (const char* scriptName = tok.Next())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check post-processing-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 post-processing scripts are not assigned yet;
|
||||
// instead we take the default post-processing scripts for the category (or global)
|
||||
if (event == qeUrlCompleted)
|
||||
{
|
||||
const char* postScript = g_Options->GetPostScript();
|
||||
if (!Util::EmptyStr(nzbInfo->GetCategory()))
|
||||
{
|
||||
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
|
||||
if (categoryObj && !Util::EmptyStr(categoryObj->GetPostScript()))
|
||||
{
|
||||
postScript = categoryObj->GetPostScript();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
m_curItem.reset();
|
||||
|
||||
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
||||
Guard guard(m_queueMutex);
|
||||
|
||||
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;
|
||||
}
|
||||
76
daemon/extension/QueueScript.h
Normal file
76
daemon/extension/QueueScript.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 QUEUESCRIPT_H
|
||||
#define QUEUESCRIPT_H
|
||||
|
||||
#include "DownloadInfo.h"
|
||||
#include "ScriptConfig.h"
|
||||
|
||||
class QueueScriptCoordinator
|
||||
{
|
||||
public:
|
||||
enum EEvent
|
||||
{
|
||||
qeFileDownloaded, // lowest priority
|
||||
qeUrlCompleted,
|
||||
qeNzbMarked,
|
||||
qeNzbAdded,
|
||||
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
|
||||
173
daemon/extension/ScanScript.cpp
Normal file
173
daemon/extension/ScanScript.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
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;
|
||||
|
||||
scriptController.ExecuteScriptList(g_Options->GetScanScript());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
56
daemon/extension/ScanScript.h
Normal file
56
daemon/extension/ScanScript.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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
|
||||
108
daemon/extension/SchedulerScript.cpp
Normal file
108
daemon/extension/SchedulerScript.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", script->GetName(), m_taskId);
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("scheduler-script %s for Task%i", script->GetName(), m_taskId);
|
||||
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
|
||||
433
daemon/extension/ScriptConfig.cpp
Normal file
433
daemon/extension/ScriptConfig.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* 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:";
|
||||
|
||||
|
||||
ScriptConfig::Script::Script(const char* name, const char* location)
|
||||
{
|
||||
m_name = name;
|
||||
m_location = location;
|
||||
m_displayName = name;
|
||||
m_postScript = false;
|
||||
m_scanScript = false;
|
||||
m_queueScript = false;
|
||||
m_schedulerScript = false;
|
||||
m_feedScript = false;
|
||||
}
|
||||
|
||||
|
||||
void ScriptConfig::InitOptions()
|
||||
{
|
||||
InitScripts();
|
||||
InitConfigTemplates();
|
||||
}
|
||||
|
||||
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 (g_Options->SplitOptionString(buf, optname, optvalue))
|
||||
{
|
||||
optEntries->emplace_back(optname, optvalue);
|
||||
}
|
||||
}
|
||||
|
||||
infile.Close();
|
||||
|
||||
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 queueEventsSignatureLen = strlen(QUEUE_EVENTS_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;
|
||||
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool skip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen);
|
||||
|
||||
if (inConfig && !skip)
|
||||
{
|
||||
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)
|
||||
{
|
||||
CharBuffer buffer(1024*10 + 1);
|
||||
|
||||
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
||||
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// check if the file contains pp-script-signature
|
||||
DiskFile infile;
|
||||
if (infile.Open(fullFilename, DiskFile::omRead))
|
||||
{
|
||||
// read first 10KB of the file and look for signature
|
||||
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
|
||||
infile.Close();
|
||||
buffer[readBytes] = '\0';
|
||||
|
||||
// split buffer into lines
|
||||
Tokenizer tok(buffer, "\n\r", true);
|
||||
while (char* line = tok.Next())
|
||||
{
|
||||
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
|
||||
strstr(line, END_SCRIPT_SIGNATURE))
|
||||
{
|
||||
bool postScript = strstr(line, POST_SCRIPT_SIGNATURE);
|
||||
bool scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
|
||||
bool queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
|
||||
bool schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
|
||||
bool feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
|
||||
if (postScript || scanScript || queueScript || schedulerScript || feedScript)
|
||||
{
|
||||
char* queueEvents = nullptr;
|
||||
if (queueScript)
|
||||
{
|
||||
while (char* line = tok.Next())
|
||||
{
|
||||
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
|
||||
{
|
||||
queueEvents = line + queueEventsSignatureLen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scripts->emplace_back(scriptName, fullFilename);
|
||||
Script& script = scripts->back();
|
||||
script.SetPostScript(postScript);
|
||||
script.SetScanScript(scanScript);
|
||||
script.SetQueueScript(queueScript);
|
||||
script.SetSchedulerScript(schedulerScript);
|
||||
script.SetFeedScript(feedScript);
|
||||
script.SetQueueEvents(queueEvents);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isSubDir)
|
||||
{
|
||||
LoadScriptDir(scripts, fullFilename, 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);
|
||||
}
|
||||
}
|
||||
106
daemon/extension/ScriptConfig.h
Normal file
106
daemon/extension/ScriptConfig.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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);
|
||||
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; }
|
||||
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_location;
|
||||
CString m_displayName;
|
||||
bool m_postScript;
|
||||
bool m_scanScript;
|
||||
bool m_queueScript;
|
||||
bool m_schedulerScript;
|
||||
bool m_feedScript;
|
||||
CString m_queueEvents;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
friend class Options;
|
||||
};
|
||||
|
||||
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 LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
|
||||
void BuildScriptDisplayNames(Scripts* scripts);
|
||||
void LoadScripts(Scripts* scripts);
|
||||
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-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,114 +14,123 @@
|
||||
* 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 "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 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;
|
||||
bool m_force = false;
|
||||
bool m_save = false;
|
||||
FeedCache m_feedCache;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
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) :
|
||||
m_fileName(fileName)
|
||||
{
|
||||
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", 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,14 +239,14 @@ 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']");
|
||||
if (tag)
|
||||
@@ -294,9 +254,9 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,9 +268,9 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,9 +281,35 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
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']");
|
||||
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']");
|
||||
if (tag)
|
||||
{
|
||||
attr = tag->Getattributes()->getNamedItem("value");
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t bval(attr->Gettext());
|
||||
int val = atoi(bval);
|
||||
feedItemInfo.SetTvmazeId(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +322,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
pFeedItemInfo->SetEpisode(val);
|
||||
feedItemInfo.SetEpisode(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +335,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
if (attr)
|
||||
{
|
||||
_bstr_t val(attr->Gettext());
|
||||
pFeedItemInfo->SetSeason(val);
|
||||
feedItemInfo.SetSeason(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
|
||||
|
||||
#else
|
||||
|
||||
FeedFile* FeedFile::Create(const char* szFileName)
|
||||
bool FeedFile::Parse()
|
||||
{
|
||||
FeedFile* pFile = new FeedFile(szFileName);
|
||||
|
||||
xmlSAXHandler SAX_handler = {0};
|
||||
SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
|
||||
SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
|
||||
@@ -383,126 +367,135 @@ 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");
|
||||
return false;
|
||||
}
|
||||
|
||||
return pFile;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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) &&
|
||||
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 +503,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 +539,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 +555,43 @@ 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)
|
||||
{
|
||||
xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
|
||||
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", 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,48 @@
|
||||
* 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);
|
||||
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;
|
||||
|
||||
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,185 @@
|
||||
* 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* feedScript) :
|
||||
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);
|
||||
m_id = id;
|
||||
m_name = name ? name : "";
|
||||
m_url = url ? url : "";
|
||||
m_filter = filter ? filter : "";
|
||||
m_filterHash = Util::HashBJ96(m_filter, strlen(m_filter), 0);
|
||||
m_category = category ? category : "";
|
||||
m_feedScript = feedScript ? feedScript : "";
|
||||
}
|
||||
|
||||
|
||||
FeedItemInfo::Attr::Attr(const char* szName, const char* szValue)
|
||||
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* name)
|
||||
{
|
||||
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++)
|
||||
for (Attr& attr : this)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedItemInfo::Attributes::Add(const char* szName, const char* szValue)
|
||||
{
|
||||
push_back(new Attr(szName, szValue));
|
||||
}
|
||||
|
||||
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* szName)
|
||||
{
|
||||
for (iterator it = begin(); it != end(); it++)
|
||||
{
|
||||
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,61 @@ 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* feedScript);
|
||||
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* GetFeedScript() { return m_feedScript; }
|
||||
time_t GetLastUpdate() { return m_lastUpdate; }
|
||||
void SetLastUpdate(time_t lastUpdate) { m_lastUpdate = lastUpdate; }
|
||||
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;
|
||||
int m_interval;
|
||||
CString m_filter;
|
||||
uint32 m_filterHash;
|
||||
bool m_pauseNzb;
|
||||
CString m_category;
|
||||
CString m_feedScript;
|
||||
int m_priority;
|
||||
time_t m_lastUpdate = 0;
|
||||
bool m_preview = false;
|
||||
EStatus m_status = fsUndefined;
|
||||
CString m_outputFilename;
|
||||
bool m_fetch = false;
|
||||
bool m_force = false;
|
||||
bool m_backlog;
|
||||
};
|
||||
|
||||
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 +113,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 +231,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-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,32 +15,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 <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 "Frontend.h"
|
||||
@@ -51,28 +29,11 @@
|
||||
#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_updateInterval = g_Options->GetUpdateInterval();
|
||||
}
|
||||
|
||||
bool Frontend::PrepareData()
|
||||
@@ -83,33 +44,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_Options->GetPauseDownload();
|
||||
m_downloadLimit = g_Options->GetDownloadRate();
|
||||
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 +78,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_Options->SetResumeTime(0);
|
||||
g_Options->SetPauseDownload(pause);
|
||||
}
|
||||
}
|
||||
|
||||
void Frontend::ServerSetDownloadRate(int iRate)
|
||||
void Frontend::ServerSetDownloadRate(int rate)
|
||||
{
|
||||
if (IsRemoteMode())
|
||||
{
|
||||
RequestSetDownloadRate(iRate);
|
||||
RequestSetDownloadRate(rate);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_pOptions->SetDownloadRate(iRate);
|
||||
g_Options->SetDownloadRate(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, offset, nullptr);
|
||||
}
|
||||
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 +161,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 +179,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 +220,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 +229,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 +240,70 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -35,48 +30,45 @@
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
public:
|
||||
Frontend();
|
||||
private:
|
||||
MessageList m_remoteMessages;
|
||||
|
||||
bool RequestMessages();
|
||||
bool RequestFileList();
|
||||
};
|
||||
|
||||
#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,14 @@
|
||||
* 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 "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 +30,7 @@ void LoggableFrontend::Run()
|
||||
while (!IsStopped())
|
||||
{
|
||||
Update();
|
||||
usleep(m_iUpdateInterval * 1000);
|
||||
usleep(m_updateInterval * 1000);
|
||||
}
|
||||
// Printing the last messages
|
||||
Update();
|
||||
@@ -79,23 +50,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 +76,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 +103,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
|
||||
|
||||
959
daemon/main/CommandLineParser.cpp
Normal file
959
daemon/main/CommandLineParser.cpp
Normal file
@@ -0,0 +1,959 @@
|
||||
/*
|
||||
* 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 "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, "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"
|
||||
" - for files (F) and groups (G):\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"
|
||||
" 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, 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 (editQueueIdTo != 0)
|
||||
{
|
||||
if (editQueueIdFrom < editQueueIdTo)
|
||||
{
|
||||
editQueueIdCount = editQueueIdTo - editQueueIdFrom + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
editQueueIdCount = editQueueIdFrom - editQueueIdTo + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
editQueueIdCount = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < editQueueIdCount; i++)
|
||||
{
|
||||
if (editQueueIdFrom < editQueueIdTo || editQueueIdTo == 0)
|
||||
{
|
||||
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-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 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
|
||||
103
daemon/main/DiskService.cpp
Normal file
103
daemon/main/DiskService.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 "DiskService.h"
|
||||
#include "Options.h"
|
||||
#include "StatMeter.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
void DiskService::ServiceWork()
|
||||
{
|
||||
m_interval++;
|
||||
if (m_interval == 5)
|
||||
{
|
||||
if (!g_Options->GetPauseDownload() &&
|
||||
g_Options->GetDiskSpace() > 0 && !g_StatMeter->GetStandBy())
|
||||
{
|
||||
// check free disk space every 1 second
|
||||
CheckDiskSpace();
|
||||
}
|
||||
m_interval = 0;
|
||||
}
|
||||
|
||||
if (m_waitingRequiredDir)
|
||||
{
|
||||
CheckRequiredDir();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskService::CheckDiskSpace()
|
||||
{
|
||||
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_Options->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_Options->SetPauseDownload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DiskService::CheckRequiredDir()
|
||||
{
|
||||
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_Options->SetTempPauseDownload(false);
|
||||
g_Options->SetTempPausePostprocess(false);
|
||||
m_waitingRequiredDir = false;
|
||||
}
|
||||
41
daemon/main/DiskService.h
Normal file
41
daemon/main/DiskService.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 DISKSERVICE_H
|
||||
#define DISKSERVICE_H
|
||||
|
||||
#include "Service.h"
|
||||
|
||||
class DiskService : public Service
|
||||
{
|
||||
protected:
|
||||
virtual int ServiceInterval() { return 200; }
|
||||
virtual void ServiceWork();
|
||||
|
||||
private:
|
||||
int m_interval = 0;
|
||||
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-2015 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)
|
||||
{
|
||||
m_updateScriptController->Detach();
|
||||
waitScript = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitScript)
|
||||
{
|
||||
while (m_updateScriptController)
|
||||
{
|
||||
usleep(20*1000);
|
||||
}
|
||||
}
|
||||
|
||||
ClearMessages();
|
||||
|
||||
free(m_szUpdateScript);
|
||||
}
|
||||
|
||||
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,126 +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))
|
||||
int 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()
|
||||
{
|
||||
// the update-script should not be automatically terminated when the program quits
|
||||
UnregisterRunningScript();
|
||||
|
||||
m_iPrefixLen = 0;
|
||||
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 ",
|
||||
}
|
||||
for (; *hexSig && *(hexSig+1);)
|
||||
{
|
||||
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-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,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,411 @@ 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
|
||||
};
|
||||
|
||||
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* postScript) :
|
||||
m_name(name), m_destDir(destDir), m_unpack(unpack), m_postScript(postScript) {}
|
||||
const char* GetName() { return m_name; }
|
||||
const char* GetDestDir() { return m_destDir; }
|
||||
bool GetUnpack() { return m_unpack; }
|
||||
const char* GetPostScript() { return m_postScript; }
|
||||
NameList* GetAliases() { return &m_aliases; }
|
||||
|
||||
private:
|
||||
CString m_name;
|
||||
CString m_destDir;
|
||||
bool m_unpack;
|
||||
CString m_postScript;
|
||||
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, 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* feedScript) {}
|
||||
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;
|
||||
bool SplitOptionString(const char* option, CString& optName, CString& optValue);
|
||||
bool GetFatalError() { return m_fatalError; }
|
||||
GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); }
|
||||
|
||||
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 GetBrokenLog() const { return m_brokenLog; }
|
||||
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 GetTerminateTimeout() { return m_terminateTimeout; }
|
||||
bool GetDecode() { return m_decode; };
|
||||
bool GetAppendCategoryDir() { return m_appendCategoryDir; }
|
||||
bool GetContinuePartial() { return m_continuePartial; }
|
||||
int GetRetries() { return m_retries; }
|
||||
int GetRetryInterval() { return m_retryInterval; }
|
||||
bool GetSaveQueue() { return m_saveQueue; }
|
||||
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 GetSecureControl() { return m_secureControl; }
|
||||
int GetSecurePort() { return m_securePort; }
|
||||
const char* GetSecureCert() { return m_secureCert; }
|
||||
const char* GetSecureKey() { return m_secureKey; }
|
||||
const char* GetAuthorizedIp() { return m_authorizedIp; }
|
||||
const char* GetLockFile() { return m_lockFile; }
|
||||
const char* GetDaemonUsername() { return m_daemonUsername; }
|
||||
EOutputMode GetOutputMode() { return m_outputMode; }
|
||||
bool GetReloadQueue() { return m_reloadQueue; }
|
||||
int GetUrlConnections() { return m_urlConnections; }
|
||||
int GetLogBufferSize() { return m_logBufferSize; }
|
||||
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; }
|
||||
bool GetParRename() { return m_parRename; }
|
||||
int GetParBuffer() { return m_parBuffer; }
|
||||
int GetParThreads() { return m_parThreads; }
|
||||
EHealthCheck GetHealthCheck() { return m_healthCheck; }
|
||||
const char* GetScriptOrder() { return m_scriptOrder; }
|
||||
const char* GetPostScript() { return m_postScript; }
|
||||
const char* GetScanScript() { return m_scanScript; }
|
||||
const char* GetQueueScript() { return m_queueScript; }
|
||||
const char* GetFeedScript() { return m_feedScript; }
|
||||
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 GetDumpCore() { return m_dumpCore; }
|
||||
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 GetAccurateRate() { return m_accurateRate; }
|
||||
bool GetUnpack() { return m_unpack; }
|
||||
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; }
|
||||
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; }
|
||||
|
||||
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; }
|
||||
void SetPauseDownload(bool pauseDownload) { m_pauseDownload = pauseDownload; }
|
||||
bool GetPauseDownload() const { return m_pauseDownload; }
|
||||
void SetPausePostProcess(bool pausePostProcess) { m_pausePostProcess = pausePostProcess; }
|
||||
bool GetPausePostProcess() const { return m_pausePostProcess; }
|
||||
void SetPauseScan(bool pauseScan) { m_pauseScan = pauseScan; }
|
||||
bool GetPauseScan() const { return m_pauseScan; }
|
||||
void SetTempPauseDownload(bool tempPauseDownload) { m_tempPauseDownload = tempPauseDownload; }
|
||||
bool GetTempPauseDownload() const { return m_tempPauseDownload; }
|
||||
bool GetTempPausePostprocess() const { return m_tempPausePostprocess; }
|
||||
void SetTempPausePostprocess(bool tempPausePostprocess) { m_tempPausePostprocess = tempPausePostprocess; }
|
||||
void SetDownloadRate(int rate) { m_downloadRate = rate; }
|
||||
int GetDownloadRate() const { return m_downloadRate; }
|
||||
void SetResumeTime(time_t resumeTime) { m_resumeTime = resumeTime; }
|
||||
time_t GetResumeTime() const { return m_resumeTime; }
|
||||
void SetLocalTimeOffset(int localTimeOffset) { m_localTimeOffset = localTimeOffset; }
|
||||
int GetLocalTimeOffset() { return m_localTimeOffset; }
|
||||
void SetQuotaReached(bool quotaReached) { m_quotaReached = quotaReached; }
|
||||
bool GetQuotaReached() { return m_quotaReached; }
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
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_decode = true;
|
||||
bool m_brokenLog = false;
|
||||
bool m_nzbLog = false;
|
||||
int m_articleTimeout = 0;
|
||||
int m_urlTimeout = 0;
|
||||
int m_terminateTimeout = 0;
|
||||
bool m_appendCategoryDir = false;
|
||||
bool m_continuePartial = false;
|
||||
int m_retries = 0;
|
||||
int m_retryInterval = 0;
|
||||
bool m_saveQueue = false;
|
||||
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;
|
||||
int m_controlPort = 0;
|
||||
bool m_secureControl = false;
|
||||
int m_securePort = 0;
|
||||
CString m_secureCert;
|
||||
CString m_secureKey;
|
||||
CString m_authorizedIp;
|
||||
CString m_lockFile;
|
||||
CString m_daemonUsername;
|
||||
EOutputMode m_outputMode = omLoggable;
|
||||
bool m_reloadQueue = false;
|
||||
int m_urlConnections = 0;
|
||||
int m_logBufferSize = 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;
|
||||
bool m_parRename = false;
|
||||
int m_parBuffer = 0;
|
||||
int m_parThreads = 0;
|
||||
EHealthCheck m_healthCheck = hcNone;
|
||||
CString m_postScript;
|
||||
CString m_scriptOrder;
|
||||
CString m_scanScript;
|
||||
CString m_queueScript;
|
||||
CString m_feedScript;
|
||||
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_dumpCore = false;
|
||||
bool m_parPauseQueue = false;
|
||||
bool m_scriptPauseQueue = false;
|
||||
bool m_nzbCleanupDisk = false;
|
||||
int m_parTimeLimit = 0;
|
||||
int m_keepHistory = 0;
|
||||
bool m_accurateRate = false;
|
||||
bool m_unpack = false;
|
||||
bool m_unpackCleanupDisk = false;
|
||||
CString m_unrarCmd;
|
||||
CString m_sevenZipCmd;
|
||||
CString m_unpackPassFile;
|
||||
bool m_unpackPauseQueue;
|
||||
CString m_extCleanupDisk;
|
||||
CString m_parIgnoreExt;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
bool m_serverMode = false;
|
||||
bool m_daemonMode = false;
|
||||
bool m_remoteClientMode = false;
|
||||
bool m_pauseDownload = false;
|
||||
bool m_pausePostProcess = false;
|
||||
bool m_pauseScan = false;
|
||||
bool m_tempPauseDownload = true;
|
||||
bool m_tempPausePostprocess = true;
|
||||
int m_downloadRate = 0;
|
||||
time_t m_resumeTime = 0;
|
||||
int m_localTimeOffset = 0;
|
||||
bool m_quotaReached = false;
|
||||
|
||||
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);
|
||||
void ConvertOldOption(CString& option, CString& value);
|
||||
};
|
||||
|
||||
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-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,29 +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"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Scheduler.h"
|
||||
#include "Options.h"
|
||||
@@ -45,92 +26,46 @@
|
||||
#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(),
|
||||
[](std::unique_ptr<Task>& task1, 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::ServiceWork()
|
||||
{
|
||||
m_bExecuteProcess = true;
|
||||
if (!DownloadQueue::IsLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_firstChecked)
|
||||
{
|
||||
FirstCheck();
|
||||
m_firstChecked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_executeProcess = true;
|
||||
CheckTasks();
|
||||
CheckScheduledResume();
|
||||
}
|
||||
@@ -139,141 +74,139 @@ 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)
|
||||
{
|
||||
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_Options->GetLocalTimeOffset();
|
||||
time_t localLastCheck = m_lastCheck + g_Options->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 = weekDayOK && localLastCheck < appoint && appoint <= localCurrent;
|
||||
|
||||
//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 (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",
|
||||
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]);
|
||||
|
||||
switch (pTask->m_eCommand)
|
||||
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_Options->SetDownloadRate(atoi(task->m_param) * 1024);
|
||||
m_downloadRateChanged = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case scPauseDownload:
|
||||
case scUnpauseDownload:
|
||||
g_pOptions->SetPauseDownload(pTask->m_eCommand == scPauseDownload);
|
||||
m_bPauseDownloadChanged = true;
|
||||
g_Options->SetPauseDownload(task->m_command == scPauseDownload);
|
||||
m_pauseDownloadChanged = true;
|
||||
break;
|
||||
|
||||
case scPausePostProcess:
|
||||
case scUnpausePostProcess:
|
||||
g_pOptions->SetPausePostProcess(pTask->m_eCommand == scPausePostProcess);
|
||||
m_bPausePostProcessChanged = true;
|
||||
g_Options->SetPausePostProcess(task->m_command == scPausePostProcess);
|
||||
m_pausePostProcessChanged = true;
|
||||
break;
|
||||
|
||||
case scPauseScan:
|
||||
case scUnpauseScan:
|
||||
g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan);
|
||||
m_bPauseScanChanged = true;
|
||||
g_Options->SetPauseScan(task->m_command == scPauseScan);
|
||||
m_pauseScanChanged = true;
|
||||
break;
|
||||
|
||||
case scScript:
|
||||
case scProcess:
|
||||
if (m_bExecuteProcess)
|
||||
if (m_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 (m_executeProcess)
|
||||
{
|
||||
FetchFeed(pTask->m_szParam);
|
||||
FetchFeed(task->m_param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -281,91 +214,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_Options->GetDownloadRate() / 1024);
|
||||
}
|
||||
if (m_bPauseDownloadChanged)
|
||||
if (m_pauseDownloadChanged)
|
||||
{
|
||||
info("Scheduler: %s download", g_pOptions->GetPauseDownload() ? "pausing" : "unpausing");
|
||||
info("Scheduler: %s download", g_Options->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_Options->GetPausePostProcess() ? "pausing" : "unpausing");
|
||||
}
|
||||
if (m_bPauseScanChanged)
|
||||
if (m_pauseScanChanged)
|
||||
{
|
||||
info("Scheduler: %s scan", g_pOptions->GetPauseScan() ? "pausing" : "unpausing");
|
||||
info("Scheduler: %s scan", g_Options->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 +304,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_Options->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_Options->SetResumeTime(0);
|
||||
g_Options->SetPauseDownload(false);
|
||||
g_Options->SetPausePostProcess(false);
|
||||
g_Options->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-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,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
|
||||
@@ -53,52 +46,53 @@ 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;
|
||||
|
||||
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 1000; }
|
||||
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;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
125
daemon/main/StackTrace.cpp
Executable file → Normal file
125
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,15 +138,15 @@ 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);
|
||||
PrintBacktrace(exPtrs->ContextRecord);
|
||||
#else
|
||||
info("Detailed exception information can be printed by debug version of NZBGet (available from download page)");
|
||||
#endif
|
||||
@@ -218,7 +185,7 @@ void PrintBacktrace()
|
||||
{
|
||||
#ifdef HAVE_BACKTRACE
|
||||
printf("Segmentation fault, tracing...\n");
|
||||
|
||||
|
||||
void *array[100];
|
||||
size_t size;
|
||||
char **strings;
|
||||
@@ -251,9 +218,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,7 +248,7 @@ void SignalProc(int iSignal)
|
||||
void InstallErrorHandler()
|
||||
{
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
if (g_pOptions->GetDumpCore())
|
||||
if (g_Options->GetDumpCore())
|
||||
{
|
||||
EnableDumpCore();
|
||||
}
|
||||
@@ -294,8 +261,8 @@ void InstallErrorHandler()
|
||||
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,7 +275,7 @@ class SegFault
|
||||
public:
|
||||
void DoSegFault()
|
||||
{
|
||||
char* N = NULL;
|
||||
char* N = nullptr;
|
||||
strcpy(N, "");
|
||||
}
|
||||
};
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
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-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,21 +14,238 @@
|
||||
* 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 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 libpar2 supports cancelling (needs a special patch) */
|
||||
#define HAVE_PAR2_CANCEL
|
||||
|
||||
/* 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. */
|
||||
#define HAVE_REGEX_H 1
|
||||
|
||||
/* Suppress warnings */
|
||||
#define _CRT_SECURE_NO_DEPRECATE
|
||||
|
||||
/* Suppress warnings */
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
|
||||
#define _USE_32BIT_TIME_T
|
||||
|
||||
#if _WIN32_WINNT < 0x0501
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#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 on 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 <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <libxml/xmlerror.h>
|
||||
#include <libxml/entities.h>
|
||||
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
#include <sys/prctl.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 <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#ifdef HAVE_LIBGNUTLS
|
||||
#ifdef WIN32
|
||||
#include <BaseTsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
typedef int pid_t;
|
||||
#endif
|
||||
#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>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/pem.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
|
||||
#ifdef HAVE_INTTYPES_H
|
||||
# include <inttypes.h>
|
||||
#endif
|
||||
#endif /* NOT DISABLE_PARCHECK */
|
||||
|
||||
|
||||
/***************** GLOBAL FUNCTION AND CONST OVERRIDES *****************/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// WINDOWS
|
||||
|
||||
#define snprintf _snprintf
|
||||
#ifndef strdup
|
||||
@@ -39,16 +256,14 @@
|
||||
#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 __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 usleep(usec) Sleep((usec) / 1000)
|
||||
#define socklen_t int
|
||||
#define SHUT_WR 0x01
|
||||
@@ -56,22 +271,25 @@
|
||||
#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
|
||||
#ifdef DEBUG
|
||||
// redefine "exit" to avoid printing memory leaks report when terminated because of wrong command line switches
|
||||
#define exit(code) ExitProcess(code)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
@@ -88,14 +306,38 @@
|
||||
#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 uint32_t int32;
|
||||
typedef uint32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef uint64_t uint64;
|
||||
#else
|
||||
typedef unsigned char uint8;
|
||||
typedef signed int int32;
|
||||
typedef unsigned int uint32;
|
||||
typedef signed long long int64;
|
||||
typedef unsigned long long uint64;
|
||||
#endif
|
||||
|
||||
typedef unsigned char uchar;
|
||||
|
||||
#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
|
||||
|
||||
#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-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,35 +15,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 <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"
|
||||
@@ -54,37 +29,23 @@
|
||||
#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);
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -114,300 +75,297 @@ 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->GetRetries() > 0 ? g_Options->GetRetries() : 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);
|
||||
m_connection = g_ServerPool->GetConnection(level, wantServer, &failedServers);
|
||||
usleep(5 * 1000);
|
||||
}
|
||||
SetLastUpdateTimeNow();
|
||||
SetStatus(adRunning);
|
||||
|
||||
if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
|
||||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
|
||||
iServerConfigGeneration != g_pServerPool->GetGeneration())
|
||||
if (IsStopped() || ((g_Options->GetPauseDownload() || g_Options->GetQuotaReached()) && !force) ||
|
||||
(g_Options->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_Options->GetPauseDownload() || g_Options->GetQuotaReached()) && !force) ||
|
||||
(g_Options->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_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';
|
||||
|
||||
for (int retry = 3; retry > 0; retry--)
|
||||
{
|
||||
szResponse = m_pConnection->Request(tmp);
|
||||
if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError())
|
||||
response = m_connection->Request(BString<1024>("ARTICLE %s\r\n", m_articleInfo->GetMessageId()));
|
||||
if ((response && !strncmp(response, "2", 1)) || m_connection->GetAuthError())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Status = CheckResponse(szResponse, "could not fetch article");
|
||||
if (Status != adFinished)
|
||||
status = CheckResponse(response, "could not fetch article");
|
||||
if (status != adFinished)
|
||||
{
|
||||
return Status;
|
||||
return status;
|
||||
}
|
||||
|
||||
if (g_pOptions->GetDecode())
|
||||
if (g_Options->GetDecode())
|
||||
{
|
||||
m_YDecoder.Clear();
|
||||
m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck());
|
||||
m_UDecoder.Clear();
|
||||
m_yDecoder.Clear();
|
||||
m_yDecoder.SetCrcCheck(g_Options->GetCrcCheck());
|
||||
m_uDecoder.Clear();
|
||||
}
|
||||
|
||||
bool bBody = false;
|
||||
bool bEnd = false;
|
||||
const int LineBufSize = 1024*10;
|
||||
char* szLineBuf = (char*)malloc(LineBufSize);
|
||||
Status = adRunning;
|
||||
bool body = false;
|
||||
bool end = false;
|
||||
CharBuffer lineBuf(1024*10);
|
||||
status = adRunning;
|
||||
|
||||
while (!IsStopped())
|
||||
{
|
||||
time_t tOldTime = m_tLastUpdateTime;
|
||||
time_t oldTime = m_lastUpdateTime;
|
||||
SetLastUpdateTimeNow();
|
||||
if (tOldTime != m_tLastUpdateTime)
|
||||
if (oldTime != m_lastUpdateTime)
|
||||
{
|
||||
AddServerData();
|
||||
}
|
||||
|
||||
// Throttle the bandwidth
|
||||
while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) &&
|
||||
(g_pStatMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate()))
|
||||
while (!IsStopped() && (g_Options->GetDownloadRate() > 0.0f) &&
|
||||
(g_StatMeter->CalcCurrentDownloadSpeed() > g_Options->GetDownloadRate() ||
|
||||
g_StatMeter->CalcMomentaryDownloadSpeed() > g_Options->GetDownloadRate()))
|
||||
{
|
||||
SetLastUpdateTimeNow();
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
|
||||
int iLen = 0;
|
||||
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
|
||||
int len = 0;
|
||||
char* line = m_connection->ReadLine(lineBuf, lineBuf.Size(), &len);
|
||||
|
||||
g_pStatMeter->AddSpeedReading(iLen);
|
||||
if (g_pOptions->GetAccurateRate())
|
||||
g_StatMeter->AddSpeedReading(len);
|
||||
if (g_Options->GetAccurateRate())
|
||||
{
|
||||
AddServerData();
|
||||
}
|
||||
@@ -417,16 +375,16 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
|
||||
{
|
||||
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"))
|
||||
{
|
||||
bEnd = true;
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -434,290 +392,292 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
|
||||
if (!strncmp(line, "..", 2))
|
||||
{
|
||||
line++;
|
||||
iLen--;
|
||||
len--;
|
||||
}
|
||||
|
||||
if (!bBody)
|
||||
if (!body)
|
||||
{
|
||||
// detect body of article
|
||||
if (*line == '\r' || *line == '\n')
|
||||
{
|
||||
bBody = true;
|
||||
body = 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 (strncmp(p, m_articleInfo->GetMessageId(), strlen(m_articleInfo->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;
|
||||
detail("Article %s @ %s failed: Wrong message-id, expected %s, returned %s", *m_infoName,
|
||||
*m_connectionName, m_articleInfo->GetMessageId(), p);
|
||||
status = adFailed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUnknown && g_pOptions->GetDecode())
|
||||
|
||||
if (m_format == Decoder::efUnknown && g_Options->GetDecode())
|
||||
{
|
||||
m_eFormat = Decoder::DetectFormat(line, iLen);
|
||||
m_format = Decoder::DetectFormat(line, len, body);
|
||||
if (m_format != Decoder::efUnknown)
|
||||
{
|
||||
// sometimes news servers misbehave and send article body without new line separator between headers and body
|
||||
// if we found decoder signature we know the body is already arrived
|
||||
body = true;
|
||||
}
|
||||
}
|
||||
|
||||
// write to output file
|
||||
if (((bBody && m_eFormat != Decoder::efUnknown) || !g_pOptions->GetDecode()) && !Write(line, iLen))
|
||||
if (((body && m_format != Decoder::efUnknown) || !g_Options->GetDecode()) && !Write(line, len))
|
||||
{
|
||||
Status = adFatalError;
|
||||
status = adFatalError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(szLineBuf);
|
||||
|
||||
if (!bEnd && Status == adRunning && !IsStopped())
|
||||
if (!end && status == adRunning && !IsStopped())
|
||||
{
|
||||
detail("Article %s @ %s failed: article incomplete", m_szInfoName, m_szConnectionName);
|
||||
Status = adFailed;
|
||||
detail("Article %s @ %s failed: article incomplete", *m_infoName, *m_connectionName);
|
||||
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* line, 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 (g_Options->GetDecode())
|
||||
{
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
if (m_format == Decoder::efYenc)
|
||||
{
|
||||
iLen = m_YDecoder.DecodeBuffer(szLine, iLen);
|
||||
szArticleFilename = m_YDecoder.GetArticleFilename();
|
||||
iArticleFileSize = m_YDecoder.GetSize();
|
||||
len = m_yDecoder.DecodeBuffer(line, len);
|
||||
articleFilename = m_yDecoder.GetArticleFilename();
|
||||
articleFileSize = m_yDecoder.GetSize();
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUx)
|
||||
else if (m_format == Decoder::efUx)
|
||||
{
|
||||
iLen = m_UDecoder.DecodeBuffer(szLine, iLen);
|
||||
szArticleFilename = m_UDecoder.GetArticleFilename();
|
||||
len = m_uDecoder.DecodeBuffer(line, len);
|
||||
articleFilename = m_uDecoder.GetArticleFilename();
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Decoding %s failed: unsupported encoding", m_szInfoName);
|
||||
detail("Decoding %s failed: unsupported encoding", *m_infoName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iLen > 0 && m_eFormat == Decoder::efYenc)
|
||||
if (len > 0 && m_format == Decoder::efYenc)
|
||||
{
|
||||
if (m_YDecoder.GetBegin() == 0 || m_YDecoder.GetEnd() == 0)
|
||||
if (m_yDecoder.GetBegin() == 0 || m_yDecoder.GetEnd() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
iArticleOffset = m_YDecoder.GetBegin() - 1;
|
||||
iArticleSize = (int)(m_YDecoder.GetEnd() - m_YDecoder.GetBegin() + 1);
|
||||
articleOffset = m_yDecoder.GetBegin() - 1;
|
||||
articleSize = (int)(m_yDecoder.GetEnd() - m_yDecoder.GetBegin() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_bWritingStarted && iLen > 0)
|
||||
if (!m_writingStarted && len > 0)
|
||||
{
|
||||
if (!m_ArticleWriter.Start(m_eFormat, szArticleFilename, iArticleFileSize, iArticleOffset, iArticleSize))
|
||||
if (!m_articleWriter.Start(m_format, articleFilename, articleFileSize, articleOffset, articleSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_bWritingStarted = true;
|
||||
m_writingStarted = true;
|
||||
}
|
||||
|
||||
bool bOK = iLen == 0 || m_ArticleWriter.Write(szLine, iLen);
|
||||
bool ok = len == 0 || m_articleWriter.Write(line, len);
|
||||
|
||||
return bOK;
|
||||
return ok;
|
||||
}
|
||||
|
||||
ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
|
||||
{
|
||||
if (g_pOptions->GetDecode())
|
||||
if (g_Options->GetDecode())
|
||||
{
|
||||
Decoder* pDecoder = NULL;
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
Decoder* decoder = nullptr;
|
||||
if (m_format == Decoder::efYenc)
|
||||
{
|
||||
pDecoder = &m_YDecoder;
|
||||
decoder = &m_yDecoder;
|
||||
}
|
||||
else if (m_eFormat == Decoder::efUx)
|
||||
else if (m_format == Decoder::efUx)
|
||||
{
|
||||
pDecoder = &m_UDecoder;
|
||||
decoder = &m_uDecoder;
|
||||
}
|
||||
else
|
||||
{
|
||||
detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName);
|
||||
detail("Decoding %s failed: no binary data or unsupported encoding format", *m_infoName);
|
||||
return adFailed;
|
||||
}
|
||||
|
||||
Decoder::EStatus eStatus = pDecoder->Check();
|
||||
Decoder::EStatus status = decoder->Check();
|
||||
|
||||
if (eStatus == Decoder::eFinished)
|
||||
if (status == Decoder::dsFinished)
|
||||
{
|
||||
if (pDecoder->GetArticleFilename())
|
||||
if (decoder->GetArticleFilename())
|
||||
{
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = strdup(pDecoder->GetArticleFilename());
|
||||
m_articleFilename = decoder->GetArticleFilename();
|
||||
}
|
||||
|
||||
if (m_eFormat == Decoder::efYenc)
|
||||
if (m_format == Decoder::efYenc)
|
||||
{
|
||||
m_pArticleInfo->SetCrc(g_pOptions->GetCrcCheck() ?
|
||||
m_YDecoder.GetCalculatedCrc() : m_YDecoder.GetExpectedCrc());
|
||||
m_articleInfo->SetCrc(g_Options->GetCrcCheck() ?
|
||||
m_yDecoder.GetCalculatedCrc() : m_yDecoder.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()
|
||||
{
|
||||
NNTPConnection* pConnection = m_pConnection;
|
||||
NntpConnection* connection = m_connection;
|
||||
bool terminated = Kill();
|
||||
if (terminated && pConnection)
|
||||
if (terminated && connection)
|
||||
{
|
||||
debug("Terminating connection");
|
||||
pConnection->SetSuppressErrors(true);
|
||||
pConnection->Cancel();
|
||||
pConnection->Disconnect();
|
||||
g_pStatMeter->AddServerData(pConnection->FetchTotalBytesRead(), pConnection->GetNewsServer()->GetID());
|
||||
g_pServerPool->FreeConnection(pConnection, true);
|
||||
connection->SetSuppressErrors(true);
|
||||
connection->Cancel();
|
||||
connection->Disconnect();
|
||||
g_StatMeter->AddServerData(connection->FetchTotalBytesRead(), connection->GetNewsServer()->GetId());
|
||||
g_ServerPool->FreeConnection(connection, true);
|
||||
}
|
||||
return terminated;
|
||||
}
|
||||
|
||||
void ArticleDownloader::FreeConnection(bool bKeepConnected)
|
||||
void ArticleDownloader::FreeConnection(bool keepConnected)
|
||||
{
|
||||
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-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,24 +15,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 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"
|
||||
|
||||
@@ -55,63 +49,62 @@ public:
|
||||
|
||||
class ArticleWriterImpl : public ArticleWriter
|
||||
{
|
||||
private:
|
||||
ArticleDownloader* m_pOwner;
|
||||
protected:
|
||||
virtual void SetLastUpdateTimeNow() { m_pOwner->SetLastUpdateTimeNow(); }
|
||||
public:
|
||||
void SetOwner(ArticleDownloader* pOwner) { m_pOwner = pOwner; }
|
||||
void SetOwner(ArticleDownloader* owner) { m_owner = owner; }
|
||||
protected:
|
||||
virtual void SetLastUpdateTimeNow() { m_owner->SetLastUpdateTimeNow(); }
|
||||
private:
|
||||
ArticleDownloader* m_owner;
|
||||
};
|
||||
|
||||
|
||||
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();
|
||||
bool Terminate();
|
||||
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 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::EFormat m_format = Decoder::efUnknown;
|
||||
YDecoder m_yDecoder;
|
||||
UDecoder m_uDecoder;
|
||||
ArticleWriterImpl m_articleWriter;
|
||||
ServerStatList m_serverStats;
|
||||
bool m_writingStarted;
|
||||
int m_downloadedSize = 0;
|
||||
|
||||
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* line, 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-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,89 +14,110 @@
|
||||
* 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);
|
||||
public:
|
||||
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();
|
||||
|
||||
protected:
|
||||
virtual void SetLastUpdateTimeNow() {}
|
||||
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();
|
||||
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();
|
||||
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;
|
||||
|
||||
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-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,30 +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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "Decoder.h"
|
||||
#include "Log.h"
|
||||
@@ -45,52 +25,37 @@
|
||||
|
||||
const char* Decoder::FormatNames[] = { "Unknown", "yEnc", "UU" };
|
||||
|
||||
Decoder::Decoder()
|
||||
{
|
||||
debug("Creating Decoder");
|
||||
|
||||
m_szArticleFilename = NULL;
|
||||
}
|
||||
|
||||
Decoder::~ Decoder()
|
||||
{
|
||||
debug("Destroying Decoder");
|
||||
|
||||
free(m_szArticleFilename);
|
||||
}
|
||||
|
||||
void Decoder::Clear()
|
||||
{
|
||||
free(m_szArticleFilename);
|
||||
m_szArticleFilename = NULL;
|
||||
m_articleFilename.Clear();
|
||||
}
|
||||
|
||||
Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
|
||||
Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len, bool inBody)
|
||||
{
|
||||
if (!strncmp(buffer, "=ybegin ", 8))
|
||||
{
|
||||
return efYenc;
|
||||
}
|
||||
|
||||
if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
|
||||
|
||||
if (inBody && (len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
|
||||
{
|
||||
return efUx;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -112,39 +77,39 @@ 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;
|
||||
m_body = false;
|
||||
m_begin = false;
|
||||
m_part = false;
|
||||
m_end = false;
|
||||
m_crc = false;
|
||||
m_expectedCRC = 0;
|
||||
m_calculatedCRC = 0xFFFFFFFF;
|
||||
m_beginPos = 0;
|
||||
m_endPos = 0;
|
||||
m_size = 0;
|
||||
m_endSize = 0;
|
||||
m_crcCheck = false;
|
||||
}
|
||||
|
||||
int YDecoder::DecodeBuffer(char* buffer, int len)
|
||||
{
|
||||
if (m_bBody && !m_bEnd)
|
||||
if (m_body && !m_end)
|
||||
{
|
||||
if (!strncmp(buffer, "=yend ", 6))
|
||||
{
|
||||
m_bEnd = true;
|
||||
char* pb = strstr(buffer, m_bPart ? " pcrc32=" : " crc32=");
|
||||
m_end = true;
|
||||
char* pb = strstr(buffer, m_part ? " pcrc32=" : " crc32=");
|
||||
if (pb)
|
||||
{
|
||||
m_bCrc = true;
|
||||
pb += 7 + (int)m_bPart; //=strlen(" crc32=") or strlen(" pcrc32=")
|
||||
m_lExpectedCRC = strtoul(pb, NULL, 16);
|
||||
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)
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_iEndSize = (long long)atoll(pb);
|
||||
m_endSize = (int64)atoll(pb);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -174,57 +139,54 @@ int YDecoder::DecodeBuffer(char* buffer, int len)
|
||||
}
|
||||
BreakLoop:
|
||||
|
||||
if (m_bCrcCheck)
|
||||
if (m_crcCheck)
|
||||
{
|
||||
m_lCalculatedCRC = Util::Crc32m(m_lCalculatedCRC, (unsigned char *)buffer, (unsigned int)(optr - buffer));
|
||||
m_calculatedCRC = Util::Crc32m(m_calculatedCRC, (uchar *)buffer, (uint32)(optr - buffer));
|
||||
}
|
||||
return optr - buffer;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (!m_bPart && !strncmp(buffer, "=ybegin ", 8))
|
||||
if (!m_part && !strncmp(buffer, "=ybegin ", 8))
|
||||
{
|
||||
m_bBegin = true;
|
||||
m_begin = 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';
|
||||
m_articleFilename = WebUtil::Latin1ToUtf8(CString(pb, pe - pb));
|
||||
}
|
||||
pb = strstr(buffer, " size=");
|
||||
if (pb)
|
||||
if (pb)
|
||||
{
|
||||
pb += 6; //=strlen(" size=")
|
||||
m_iSize = (long long)atoll(pb);
|
||||
m_size = (int64)atoll(pb);
|
||||
}
|
||||
m_bPart = strstr(buffer, " part=");
|
||||
if (!m_bPart)
|
||||
m_part = strstr(buffer, " part=");
|
||||
if (!m_part)
|
||||
{
|
||||
m_bBody = true;
|
||||
m_iBegin = 1;
|
||||
m_iEnd = m_iSize;
|
||||
m_body = true;
|
||||
m_beginPos = 1;
|
||||
m_endPos = m_size;
|
||||
}
|
||||
}
|
||||
else if (m_bPart && !strncmp(buffer, "=ypart ", 7))
|
||||
else if (m_part && !strncmp(buffer, "=ypart ", 7))
|
||||
{
|
||||
m_bPart = true;
|
||||
m_bBody = true;
|
||||
m_part = true;
|
||||
m_body = true;
|
||||
char* pb = strstr(buffer, " begin=");
|
||||
if (pb)
|
||||
if (pb)
|
||||
{
|
||||
pb += 7; //=strlen(" begin=")
|
||||
m_iBegin = (long long)atoll(pb);
|
||||
m_beginPos = (int64)atoll(pb);
|
||||
}
|
||||
pb = strstr(buffer, " end=");
|
||||
if (pb)
|
||||
if (pb)
|
||||
{
|
||||
pb += 5; //=strlen(" end=")
|
||||
m_iEnd = (long long)atoll(pb);
|
||||
m_endPos = (int64)atoll(pb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,29 +196,29 @@ BreakLoop:
|
||||
|
||||
Decoder::EStatus YDecoder::Check()
|
||||
{
|
||||
m_lCalculatedCRC ^= 0xFFFFFFFF;
|
||||
m_calculatedCRC ^= 0xFFFFFFFF;
|
||||
|
||||
debug("Expected crc32=%x", m_lExpectedCRC);
|
||||
debug("Calculated crc32=%x", m_lCalculatedCRC);
|
||||
debug("Expected crc32=%x", m_expectedCRC);
|
||||
debug("Calculated crc32=%x", m_calculatedCRC);
|
||||
|
||||
if (!m_bBegin)
|
||||
if (!m_begin)
|
||||
{
|
||||
return eNoBinaryData;
|
||||
return dsNoBinaryData;
|
||||
}
|
||||
else if (!m_bEnd)
|
||||
else if (!m_end)
|
||||
{
|
||||
return eArticleIncomplete;
|
||||
return dsArticleIncomplete;
|
||||
}
|
||||
else if (!m_bPart && m_iSize != m_iEndSize)
|
||||
else if (!m_part && m_size != m_endSize)
|
||||
{
|
||||
return eInvalidSize;
|
||||
return dsInvalidSize;
|
||||
}
|
||||
else if (m_bCrcCheck && m_bCrc && (m_lExpectedCRC != m_lCalculatedCRC))
|
||||
else if (m_crcCheck && m_crc && (m_expectedCRC != m_calculatedCRC))
|
||||
{
|
||||
return eCrcError;
|
||||
return dsCrcError;
|
||||
}
|
||||
|
||||
return eFinished;
|
||||
return dsFinished;
|
||||
}
|
||||
|
||||
|
||||
@@ -266,15 +228,15 @@ Decoder::EStatus YDecoder::Check()
|
||||
|
||||
UDecoder::UDecoder()
|
||||
{
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
void UDecoder::Clear()
|
||||
{
|
||||
Decoder::Clear();
|
||||
|
||||
m_bBody = false;
|
||||
m_bEnd = false;
|
||||
m_body = false;
|
||||
m_end = false;
|
||||
}
|
||||
|
||||
/* DecodeBuffer-function uses portions of code from tool UUDECODE by Clem Dye
|
||||
@@ -288,7 +250,7 @@ void UDecoder::Clear()
|
||||
|
||||
int UDecoder::DecodeBuffer(char* buffer, int len)
|
||||
{
|
||||
if (!m_bBody)
|
||||
if (!m_body)
|
||||
{
|
||||
if (!strncmp(buffer, "begin ", 6))
|
||||
{
|
||||
@@ -296,35 +258,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, 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,23 +291,23 @@ 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)
|
||||
if (effLen >= 1)
|
||||
{
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
|
||||
}
|
||||
if (iEffLen >= 2)
|
||||
if (effLen >= 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,10 +320,10 @@ int UDecoder::DecodeBuffer(char* buffer, int len)
|
||||
|
||||
Decoder::EStatus UDecoder::Check()
|
||||
{
|
||||
if (!m_bBody)
|
||||
if (!m_body)
|
||||
{
|
||||
return eNoBinaryData;
|
||||
return dsNoBinaryData;
|
||||
}
|
||||
|
||||
return eFinished;
|
||||
return dsFinished;
|
||||
}
|
||||
|
||||
@@ -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,29 +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 DECODER_H
|
||||
#define DECODER_H
|
||||
|
||||
#include "NString.h"
|
||||
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
enum EStatus
|
||||
{
|
||||
eUnknownError,
|
||||
eFinished,
|
||||
eArticleIncomplete,
|
||||
eCrcError,
|
||||
eInvalidSize,
|
||||
eNoBinaryData
|
||||
dsUnknownError,
|
||||
dsFinished,
|
||||
dsArticleIncomplete,
|
||||
dsCrcError,
|
||||
dsInvalidSize,
|
||||
dsNoBinaryData
|
||||
};
|
||||
|
||||
enum EFormat
|
||||
@@ -48,59 +45,57 @@ public:
|
||||
|
||||
static const char* FormatNames[];
|
||||
|
||||
protected:
|
||||
char* m_szArticleFilename;
|
||||
virtual ~Decoder() {}
|
||||
virtual EStatus Check() = 0;
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len) = 0;
|
||||
const char* GetArticleFilename() { return m_articleFilename; }
|
||||
static EFormat DetectFormat(const char* buffer, int len, bool inBody);
|
||||
|
||||
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);
|
||||
protected:
|
||||
CString m_articleFilename;
|
||||
};
|
||||
|
||||
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; }
|
||||
YDecoder();
|
||||
virtual EStatus Check();
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len);
|
||||
void SetCrcCheck(bool crcCheck) { m_crcCheck = crcCheck; }
|
||||
int64 GetBegin() { return m_beginPos; }
|
||||
int64 GetEnd() { return m_endPos; }
|
||||
int64 GetSize() { return m_size; }
|
||||
uint32 GetExpectedCrc() { return m_expectedCRC; }
|
||||
uint32 GetCalculatedCrc() { return m_calculatedCRC; }
|
||||
|
||||
private:
|
||||
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;
|
||||
bool m_crcCheck;
|
||||
};
|
||||
|
||||
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);
|
||||
UDecoder();
|
||||
virtual EStatus Check();
|
||||
virtual void Clear();
|
||||
virtual int DecodeBuffer(char* buffer, int len);
|
||||
|
||||
private:
|
||||
bool m_body;
|
||||
bool m_end;
|
||||
};
|
||||
|
||||
#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,
|
||||
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_port(port), m_level(level), m_normLevel(level),
|
||||
m_group(group), m_maxConnections(maxConnections), m_joinGroup(joinGroup), m_tls(tls),
|
||||
m_name(name), m_host(host ? host : ""), m_user(user ? user : ""), m_password(pass ? pass : ""),
|
||||
m_cipher(cipher ? cipher : ""), m_retention(retention), 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,59 @@
|
||||
#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,
|
||||
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; }
|
||||
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;
|
||||
int m_group;
|
||||
CString m_host;
|
||||
int m_port;
|
||||
CString m_user;
|
||||
CString m_password;
|
||||
int m_maxConnections;
|
||||
int m_level;
|
||||
int m_normLevel;
|
||||
bool m_joinGroup;
|
||||
bool m_tls;
|
||||
CString m_cipher;
|
||||
int m_retention;
|
||||
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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!m_broken)
|
||||
{
|
||||
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-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,200 +14,154 @@
|
||||
* 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 "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_Options->GetLocalTimeOffset();
|
||||
time_t locDataTime = m_dataTime + g_Options->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]=%lli ", 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]=%lli ", 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]=%lli ", 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]=%lli ", m_firstDay + i, m_bytesPerDays[i]);
|
||||
}
|
||||
info("Days: %s", szSec);
|
||||
info("Days: %s", *msg);
|
||||
}
|
||||
|
||||
StatMeter::StatMeter()
|
||||
@@ -215,61 +169,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_Options->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_Options->GetLocalTimeOffset(), (int)localTimeDelta, g_Options->GetTimeCorrection());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -279,20 +201,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 +222,324 @@ 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;
|
||||
|
||||
Guard guard(g_Options->GetAccurateRate() ? &m_speedMutex : nullptr);
|
||||
|
||||
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: %lli", m_speedTotalBytes);
|
||||
info(" SpeedBytesIndex: %i", m_speedBytesIndex);
|
||||
info(" AllBytes: %lli", 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_Options->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_Options->GetQuotaReached())
|
||||
{
|
||||
warn("Monthly quota reached at %s", *Util::FormatSize(monthBytes));
|
||||
}
|
||||
else if (dailyQuotaReached && !g_Options->GetQuotaReached())
|
||||
{
|
||||
warn("Daily quota reached at %s", *Util::FormatSize(dayBytes));
|
||||
}
|
||||
else if (!monthlyQuotaReached && !dailyQuotaReached && g_Options->GetQuotaReached())
|
||||
{
|
||||
info("Quota lifted");
|
||||
}
|
||||
|
||||
g_Options->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_Options->GetLocalTimeOffset();
|
||||
int daySlot = 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_Options->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 = (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,121 @@
|
||||
* 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);
|
||||
bool GetStandBy() { return m_standBy; }
|
||||
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;
|
||||
Mutex m_speedMutex;
|
||||
|
||||
// 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
|
||||
|
||||
229
daemon/postprocess/Cleanup.cpp
Normal file
229
daemon/postprocess/Cleanup.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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->SetStage(PostInfo::ptQueued);
|
||||
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->SetStage(PostInfo::ptQueued);
|
||||
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);
|
||||
}
|
||||
64
daemon/postprocess/Cleanup.h
Normal file
64
daemon/postprocess/Cleanup.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 CLEANUP_H
|
||||
#define CLEANUP_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Log.h"
|
||||
#include "Thread.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "Script.h"
|
||||
|
||||
class MoveController : public Thread, public ScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static void StartJob(PostInfo* postInfo);
|
||||
|
||||
protected:
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
PostInfo* m_postInfo;
|
||||
CString m_interDir;
|
||||
CString m_destDir;
|
||||
|
||||
bool MoveFiles();
|
||||
};
|
||||
|
||||
class CleanupController : public Thread, public ScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static void StartJob(PostInfo* postInfo);
|
||||
|
||||
protected:
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
PostInfo* m_postInfo;
|
||||
CString m_destDir;
|
||||
CString m_finalDir;
|
||||
|
||||
bool Cleanup(const char* destDir, bool *deleted);
|
||||
};
|
||||
|
||||
#endif
|
||||
207
daemon/postprocess/DupeMatcher.cpp
Normal file
207
daemon/postprocess/DupeMatcher.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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 "DupeMatcher.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Options.h"
|
||||
#include "Script.h"
|
||||
#include "Thread.h"
|
||||
|
||||
class RarLister : public Thread, public ScriptController
|
||||
{
|
||||
private:
|
||||
DupeMatcher* m_owner;
|
||||
int64 m_maxSize;
|
||||
bool m_compressed;
|
||||
bool m_lastSizeMax;
|
||||
int64 m_expectedSize;
|
||||
char* m_filenameBuf;
|
||||
int m_filenameBufLen;
|
||||
BString<1024> m_lastFilename;
|
||||
|
||||
protected:
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
|
||||
public:
|
||||
virtual void Run();
|
||||
static bool FindLargestFile(DupeMatcher* owner, const char* directory,
|
||||
char* filenameBuf, int filenameBufLen, int64 expectedSize,
|
||||
int timeoutSec, int64* maxSize, bool* compressed);
|
||||
};
|
||||
|
||||
bool RarLister::FindLargestFile(DupeMatcher* owner, const char* directory,
|
||||
char* filenameBuf, int filenameBufLen, int64 expectedSize,
|
||||
int timeoutSec, int64* maxSize, bool* compressed)
|
||||
{
|
||||
RarLister unrar;
|
||||
unrar.m_owner = owner;
|
||||
unrar.m_expectedSize = expectedSize;
|
||||
unrar.m_maxSize = -1;
|
||||
unrar.m_compressed = false;
|
||||
unrar.m_lastSizeMax = false;
|
||||
unrar.m_filenameBuf = filenameBuf;
|
||||
unrar.m_filenameBufLen = filenameBufLen;
|
||||
|
||||
std::vector<CString> cmdArgs = Util::SplitCommandLine(g_Options->GetUnrarCmd());
|
||||
if (cmdArgs.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const char* unrarPath = cmdArgs[0];
|
||||
unrar.SetArgs({unrarPath, "lt", "*.rar"});
|
||||
unrar.SetWorkingDir(directory);
|
||||
|
||||
time_t curTime = Util::CurrentTime();
|
||||
|
||||
unrar.Start();
|
||||
|
||||
// wait up to iTimeoutSec for unrar output
|
||||
while (unrar.IsRunning() &&
|
||||
curTime + timeoutSec > Util::CurrentTime() &&
|
||||
curTime >= Util::CurrentTime()) // in a case clock was changed
|
||||
{
|
||||
usleep(200 * 1000);
|
||||
}
|
||||
|
||||
if (unrar.IsRunning())
|
||||
{
|
||||
unrar.Terminate();
|
||||
}
|
||||
|
||||
// wait until terminated or killed
|
||||
while (unrar.IsRunning())
|
||||
{
|
||||
usleep(200 * 1000);
|
||||
}
|
||||
|
||||
*maxSize = unrar.m_maxSize;
|
||||
*compressed = unrar.m_compressed;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RarLister::Run()
|
||||
{
|
||||
Execute();
|
||||
}
|
||||
|
||||
void RarLister::AddMessage(Message::EKind kind, const char* text)
|
||||
{
|
||||
if (!strncasecmp(text, "Archive: ", 9))
|
||||
{
|
||||
m_owner->PrintMessage(Message::mkDetail, "Reading file %s", text + 9);
|
||||
}
|
||||
else if (!strncasecmp(text, " Name: ", 14))
|
||||
{
|
||||
m_lastFilename = text + 14;
|
||||
}
|
||||
else if (!strncasecmp(text, " Size: ", 14))
|
||||
{
|
||||
m_lastSizeMax = false;
|
||||
int64 size = atoll(text + 14);
|
||||
if (size > m_maxSize)
|
||||
{
|
||||
m_maxSize = size;
|
||||
m_lastSizeMax = true;
|
||||
strncpy(m_filenameBuf, m_lastFilename, m_filenameBufLen);
|
||||
m_filenameBuf[m_filenameBufLen-1] = '\0';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_lastSizeMax && !strncasecmp(text, " Compression: ", 14))
|
||||
{
|
||||
m_compressed = !strstr(text, " -m0");
|
||||
if (m_maxSize > m_expectedSize ||
|
||||
DupeMatcher::SizeDiffOK(m_maxSize, m_expectedSize, 20))
|
||||
{
|
||||
// alread found the largest file, aborting unrar
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DupeMatcher::SizeDiffOK(int64 size1, int64 size2, int maxDiffPercent)
|
||||
{
|
||||
if (size1 == 0 || size2 == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int64 diff = size1 - size2;
|
||||
diff = diff > 0 ? diff : -diff;
|
||||
int64 max = size1 > size2 ? size1 : size2;
|
||||
int diffPercent = (int)(diff * 100 / max);
|
||||
return diffPercent < maxDiffPercent;
|
||||
}
|
||||
|
||||
bool DupeMatcher::Prepare()
|
||||
{
|
||||
char filename[1024];
|
||||
FindLargestFile(m_destDir, filename, sizeof(filename), &m_maxSize, &m_compressed);
|
||||
bool sizeOK = SizeDiffOK(m_maxSize, m_expectedSize, 20);
|
||||
PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s",
|
||||
filename, m_maxSize, sizeOK ? "" : ", size mismatch");
|
||||
return sizeOK;
|
||||
}
|
||||
|
||||
bool DupeMatcher::MatchDupeContent(const char* dupeDir)
|
||||
{
|
||||
int64 dupeMaxSize = 0;
|
||||
bool dupeCompressed = false;
|
||||
char filename[1024];
|
||||
FindLargestFile(dupeDir, filename, sizeof(filename), &dupeMaxSize, &dupeCompressed);
|
||||
bool ok = dupeMaxSize == m_maxSize && dupeCompressed == m_compressed;
|
||||
PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s",
|
||||
filename, m_maxSize, ok ? "" : ", size mismatch");
|
||||
return ok;
|
||||
}
|
||||
|
||||
void DupeMatcher::FindLargestFile(const char* directory, char* filenameBuf, int bufLen,
|
||||
int64* maxSize, bool* compressed)
|
||||
{
|
||||
*maxSize = 0;
|
||||
*compressed = false;
|
||||
|
||||
DirBrowser dir(directory);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
|
||||
|
||||
int64 fileSize = FileSystem::FileSize(fullFilename);
|
||||
if (fileSize > *maxSize)
|
||||
{
|
||||
*maxSize = fileSize;
|
||||
strncpy(filenameBuf, filename, bufLen);
|
||||
filenameBuf[bufLen-1] = '\0';
|
||||
}
|
||||
|
||||
if (Util::MatchFileExt(filename, ".rar", ","))
|
||||
{
|
||||
RarLister::FindLargestFile(this, directory, filenameBuf, bufLen,
|
||||
m_maxSize, 60, maxSize, compressed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
daemon/postprocess/DupeMatcher.h
Normal file
51
daemon/postprocess/DupeMatcher.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 DUPEMATCHER_H
|
||||
#define DUPEMATCHER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Log.h"
|
||||
|
||||
class DupeMatcher
|
||||
{
|
||||
public:
|
||||
DupeMatcher(const char* destDir, int64 expectedSize) :
|
||||
m_destDir(destDir), m_expectedSize(expectedSize) {}
|
||||
bool Prepare();
|
||||
bool MatchDupeContent(const char* dupeDir);
|
||||
static bool SizeDiffOK(int64 size1, int64 size2, int maxDiffPercent);
|
||||
|
||||
protected:
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
|
||||
|
||||
private:
|
||||
CString m_destDir;
|
||||
int64 m_expectedSize;
|
||||
int64 m_maxSize = -1;
|
||||
bool m_compressed = false;
|
||||
|
||||
void FindLargestFile(const char* directory, char* filenameBuf, int bufLen,
|
||||
int64* maxSize, bool* compressed);
|
||||
|
||||
friend class RarLister;
|
||||
};
|
||||
|
||||
#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-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/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -28,13 +23,14 @@
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Container.h"
|
||||
#include "Thread.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Log.h"
|
||||
|
||||
class Repairer;
|
||||
|
||||
class ParChecker : public Thread
|
||||
{
|
||||
public:
|
||||
@@ -54,6 +50,70 @@ public:
|
||||
ptVerifyingRepaired,
|
||||
};
|
||||
|
||||
class AbstractRepairer
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractRepairer() {};
|
||||
virtual Repairer* GetRepairer() = 0;
|
||||
};
|
||||
|
||||
virtual ~ParChecker();
|
||||
virtual void Run();
|
||||
void SetDestDir(const char* destDir) { m_destDir = destDir; }
|
||||
const char* GetParFilename() { return m_parFilename; }
|
||||
const char* GetInfoName() { return m_infoName; }
|
||||
void SetInfoName(const char* infoName) { m_infoName = infoName; }
|
||||
void SetNzbName(const char* nzbName) { m_nzbName = nzbName; }
|
||||
void SetParQuick(bool parQuick) { m_parQuick = parQuick; }
|
||||
bool GetParQuick() { return m_parQuick; }
|
||||
void SetForceRepair(bool forceRepair) { m_forceRepair = forceRepair; }
|
||||
bool GetForceRepair() { return m_forceRepair; }
|
||||
void SetParFull(bool parFull) { m_parFull = parFull; }
|
||||
bool GetParFull() { return m_parFull; }
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void AddParFile(const char* parFilename);
|
||||
void QueueChanged();
|
||||
void Cancel();
|
||||
bool GetCancelled() { return m_cancelled; }
|
||||
|
||||
protected:
|
||||
class Segment
|
||||
{
|
||||
public:
|
||||
Segment(bool success, int64 offset, int size, uint32 crc) :
|
||||
m_success(success), m_offset(offset), m_size(size), m_crc(crc) {}
|
||||
bool GetSuccess() { return m_success; }
|
||||
int64 GetOffset() { return m_offset; }
|
||||
int GetSize() { return m_size; }
|
||||
uint32 GetCrc() { return m_crc; }
|
||||
|
||||
private:
|
||||
bool m_success;
|
||||
int64 m_offset;
|
||||
int m_size;
|
||||
uint32 m_crc;
|
||||
};
|
||||
|
||||
typedef std::deque<Segment> SegmentList;
|
||||
|
||||
class DupeSource
|
||||
{
|
||||
public:
|
||||
DupeSource(int id, const char* directory) :
|
||||
m_id(id), m_directory(directory) {}
|
||||
int GetId() { return m_id; }
|
||||
const char* GetDirectory() { return m_directory; }
|
||||
int GetUsedBlocks() { return m_usedBlocks; }
|
||||
void SetUsedBlocks(int usedBlocks) { m_usedBlocks = usedBlocks; }
|
||||
|
||||
private:
|
||||
int m_id;
|
||||
CString m_directory;
|
||||
int m_usedBlocks = 0;
|
||||
};
|
||||
|
||||
typedef std::deque<DupeSource> DupeSourceList;
|
||||
|
||||
enum EFileStatus
|
||||
{
|
||||
fsUnknown,
|
||||
@@ -62,125 +122,106 @@ public:
|
||||
fsFailure
|
||||
};
|
||||
|
||||
class Segment
|
||||
{
|
||||
private:
|
||||
bool m_bSuccess;
|
||||
long long m_iOffset;
|
||||
int m_iSize;
|
||||
unsigned long m_lCrc;
|
||||
|
||||
public:
|
||||
Segment(bool bSuccess, long long iOffset, int iSize, unsigned long lCrc);
|
||||
bool GetSuccess() { return m_bSuccess; }
|
||||
long long GetOffset() { return m_iOffset; }
|
||||
int GetSize() { return m_iSize; }
|
||||
unsigned long GetCrc() { return m_lCrc; }
|
||||
};
|
||||
|
||||
typedef std::deque<Segment*> SegmentListBase;
|
||||
|
||||
class SegmentList : public SegmentListBase
|
||||
{
|
||||
public:
|
||||
~SegmentList();
|
||||
};
|
||||
|
||||
typedef std::deque<char*> FileList;
|
||||
typedef std::deque<void*> SourceList;
|
||||
typedef std::vector<bool> ValidBlocks;
|
||||
|
||||
friend class Repairer;
|
||||
|
||||
private:
|
||||
char* m_szInfoName;
|
||||
char* m_szDestDir;
|
||||
char* m_szNZBName;
|
||||
const char* m_szParFilename;
|
||||
EStatus m_eStatus;
|
||||
EStage m_eStage;
|
||||
// declared as void* to prevent the including of libpar2-headers into this header-file
|
||||
void* m_pRepairer;
|
||||
char* m_szErrMsg;
|
||||
FileList m_QueuedParFiles;
|
||||
Mutex m_mutexQueuedParFiles;
|
||||
bool m_bQueuedParFilesChanged;
|
||||
FileList m_ProcessedFiles;
|
||||
int m_iProcessedFiles;
|
||||
int m_iFilesToRepair;
|
||||
int m_iExtraFiles;
|
||||
bool m_bVerifyingExtraFiles;
|
||||
char* m_szProgressLabel;
|
||||
int m_iFileProgress;
|
||||
int m_iStageProgress;
|
||||
bool m_bCancelled;
|
||||
SourceList m_sourceFiles;
|
||||
std::string m_lastFilename;
|
||||
bool m_bHasDamagedFiles;
|
||||
bool m_bParQuick;
|
||||
bool m_bForceRepair;
|
||||
|
||||
void Cleanup();
|
||||
EStatus RunParCheckAll();
|
||||
EStatus RunParCheck(const char* szParFilename);
|
||||
int PreProcessPar();
|
||||
bool LoadMainParBak();
|
||||
int ProcessMorePars();
|
||||
bool LoadMorePars();
|
||||
bool AddSplittedFragments();
|
||||
bool AddMissingFiles();
|
||||
bool IsProcessedFile(const char* szFilename);
|
||||
void WriteBrokenLog(EStatus eStatus);
|
||||
void SaveSourceList();
|
||||
void DeleteLeftovers();
|
||||
void signal_filename(std::string str);
|
||||
void signal_progress(double progress);
|
||||
void signal_done(std::string str, int available, int total);
|
||||
// declared as void* to prevent the including of libpar2-headers into this header-file
|
||||
// DiskFile* pDiskfile, Par2RepairerSourceFile* pSourcefile
|
||||
EFileStatus VerifyDataFile(void* pDiskfile, void* pSourcefile, int* pAvailableBlocks);
|
||||
bool VerifySuccessDataFile(void* pDiskfile, void* pSourcefile, unsigned long lDownloadCrc);
|
||||
bool VerifyPartialDataFile(void* pDiskfile, void* pSourcefile, SegmentList* pSegments, ValidBlocks* pValidBlocks);
|
||||
bool SmartCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, SegmentList* pSegments,
|
||||
unsigned long* pDownloadCrc);
|
||||
bool DumbCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, unsigned long* pDownloadCrc);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Unpause par2-files
|
||||
* returns true, if the files with required number of blocks were unpaused,
|
||||
* or false if there are no more files in queue for this collection or not enough blocks
|
||||
*/
|
||||
virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound) = 0;
|
||||
virtual void UpdateProgress() {}
|
||||
virtual void Completed() {}
|
||||
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {}
|
||||
virtual void RegisterParredFile(const char* szFilename) {}
|
||||
virtual bool IsParredFile(const char* szFilename) { return false; }
|
||||
virtual EFileStatus FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments) { return fsUnknown; }
|
||||
EStage GetStage() { return m_eStage; }
|
||||
const char* GetProgressLabel() { return m_szProgressLabel; }
|
||||
int GetFileProgress() { return m_iFileProgress; }
|
||||
int GetStageProgress() { return m_iStageProgress; }
|
||||
virtual bool RequestMorePars(int blockNeeded, int* blockFound) = 0;
|
||||
virtual void UpdateProgress() {}
|
||||
virtual void Completed() {}
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
|
||||
virtual void RegisterParredFile(const char* filename) {}
|
||||
virtual bool IsParredFile(const char* filename) { return false; }
|
||||
virtual EFileStatus FindFileCrc(const char* filename, uint32* crc, SegmentList* segments) { return fsUnknown; }
|
||||
virtual void RequestDupeSources(DupeSourceList* dupeSourceList) {}
|
||||
virtual void StatDupeSources(DupeSourceList* dupeSourceList) {}
|
||||
EStage GetStage() { return m_stage; }
|
||||
const char* GetProgressLabel() { return m_progressLabel; }
|
||||
int GetFileProgress() { return m_fileProgress; }
|
||||
int GetStageProgress() { return m_stageProgress; }
|
||||
|
||||
public:
|
||||
ParChecker();
|
||||
virtual ~ParChecker();
|
||||
virtual void Run();
|
||||
void SetDestDir(const char* szDestDir);
|
||||
const char* GetParFilename() { return m_szParFilename; }
|
||||
const char* GetInfoName() { return m_szInfoName; }
|
||||
void SetInfoName(const char* szInfoName);
|
||||
void SetNZBName(const char* szNZBName);
|
||||
void SetParQuick(bool bParQuick) { m_bParQuick = bParQuick; }
|
||||
bool GetParQuick() { return m_bParQuick; }
|
||||
void SetForceRepair(bool bForceRepair) { m_bForceRepair = bForceRepair; }
|
||||
bool GetForceRepair() { return m_bForceRepair; }
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void AddParFile(const char* szParFilename);
|
||||
void QueueChanged();
|
||||
void Cancel();
|
||||
bool GetCancelled() { return m_bCancelled; }
|
||||
private:
|
||||
class StreamBuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
StreamBuf(ParChecker* owner, Message::EKind kind) : m_owner(owner), m_kind(kind) {}
|
||||
virtual int overflow(int ch) override;
|
||||
private:
|
||||
ParChecker* m_owner;
|
||||
Message::EKind m_kind;
|
||||
StringBuilder m_buffer;
|
||||
};
|
||||
|
||||
typedef std::deque<CString> FileList;
|
||||
typedef std::deque<void*> SourceList;
|
||||
typedef std::vector<bool> ValidBlocks;
|
||||
|
||||
CString m_infoName;
|
||||
CString m_destDir;
|
||||
CString m_nzbName;
|
||||
const char* m_parFilename = nullptr;
|
||||
EStatus m_status = psFailed;
|
||||
EStage m_stage;
|
||||
CString m_errMsg;
|
||||
FileList m_queuedParFiles;
|
||||
Mutex m_queuedParFilesMutex;
|
||||
bool m_queuedParFilesChanged;
|
||||
FileList m_processedFiles;
|
||||
int m_processedCount;
|
||||
int m_filesToRepair;
|
||||
int m_extraFiles;
|
||||
int m_quickFiles;
|
||||
bool m_verifyingExtraFiles;
|
||||
CString m_progressLabel;
|
||||
int m_fileProgress;
|
||||
int m_stageProgress;
|
||||
bool m_cancelled;
|
||||
SourceList m_sourceFiles;
|
||||
std::string m_lastFilename;
|
||||
bool m_hasDamagedFiles;
|
||||
bool m_parQuick = false;
|
||||
bool m_forceRepair = false;
|
||||
bool m_parFull = false;
|
||||
DupeSourceList m_dupeSources;
|
||||
StreamBuf m_parOutStream{this, Message::mkDetail};
|
||||
StreamBuf m_parErrStream{this, Message::mkError};
|
||||
|
||||
// "m_repairer" should be of type "Par2::Par2Repairer", however to prevent the
|
||||
// including of libpar2-headers into this header-file we use an empty abstract class.
|
||||
std::unique_ptr<AbstractRepairer> m_repairer;
|
||||
Repairer* GetRepairer() { return m_repairer->GetRepairer(); }
|
||||
|
||||
void Cleanup();
|
||||
EStatus RunParCheckAll();
|
||||
EStatus RunParCheck(const char* parFilename);
|
||||
int PreProcessPar();
|
||||
bool LoadMainParBak();
|
||||
int ProcessMorePars();
|
||||
bool LoadMorePars();
|
||||
bool AddSplittedFragments();
|
||||
bool AddMissingFiles();
|
||||
bool AddDupeFiles();
|
||||
bool AddExtraFiles(bool onlyMissing, bool externalDir, const char* directory);
|
||||
bool IsProcessedFile(const char* filename);
|
||||
void WriteBrokenLog(EStatus status);
|
||||
void SaveSourceList();
|
||||
void DeleteLeftovers();
|
||||
void signal_filename(std::string str);
|
||||
void signal_progress(int progress);
|
||||
void signal_done(std::string str, int available, int total);
|
||||
// declared as void* to prevent the including of libpar2-headers into this header-file
|
||||
// Par2::DiskFile* pDiskfile, Par2::Par2RepairerSourceFile* pSourcefile
|
||||
EFileStatus VerifyDataFile(void* diskfile, void* sourcefile, int* availableBlocks);
|
||||
bool VerifySuccessDataFile(void* diskfile, void* sourcefile, uint32 downloadCrc);
|
||||
bool VerifyPartialDataFile(void* diskfile, void* sourcefile, SegmentList* segments, ValidBlocks* validBlocks);
|
||||
void SortExtraFiles(void* extrafiles);
|
||||
bool SmartCalcFileRangeCrc(DiskFile& file, int64 start, int64 end, SegmentList* segments,
|
||||
uint32* downloadCrc);
|
||||
bool DumbCalcFileRangeCrc(DiskFile& file, int64 start, int64 end, uint32* downloadCrc);
|
||||
void CheckEmptyFiles();
|
||||
|
||||
friend class Repairer;
|
||||
};
|
||||
|
||||
#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-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,128 +14,129 @@
|
||||
* 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 PARCOORDINATOR_H
|
||||
#define PARCOORDINATOR_H
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include "DownloadInfo.h"
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
#include "ParChecker.h"
|
||||
#include "ParRenamer.h"
|
||||
#include "DupeMatcher.h"
|
||||
#endif
|
||||
|
||||
class ParCoordinator
|
||||
class ParCoordinator
|
||||
{
|
||||
private:
|
||||
public:
|
||||
ParCoordinator();
|
||||
virtual ~ParCoordinator();
|
||||
void PausePars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
bool AddPar(FileInfo* fileInfo, bool deleted);
|
||||
void StartParCheckJob(PostInfo* postInfo);
|
||||
void StartParRenameJob(PostInfo* postInfo);
|
||||
void Stop();
|
||||
bool Cancel();
|
||||
|
||||
protected:
|
||||
void UpdateParCheckProgress();
|
||||
void UpdateParRenameProgress();
|
||||
void ParCheckCompleted();
|
||||
void ParRenameCompleted();
|
||||
void CheckPauseState(PostInfo* postInfo);
|
||||
bool RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFound);
|
||||
|
||||
private:
|
||||
class PostParChecker: public ParChecker
|
||||
{
|
||||
private:
|
||||
ParCoordinator* m_pOwner;
|
||||
PostInfo* m_pPostInfo;
|
||||
time_t m_tParTime;
|
||||
time_t m_tRepairTime;
|
||||
int m_iDownloadSec;
|
||||
protected:
|
||||
virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound);
|
||||
virtual void UpdateProgress();
|
||||
virtual void Completed() { m_pOwner->ParCheckCompleted(); }
|
||||
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...);
|
||||
virtual void RegisterParredFile(const char* szFilename);
|
||||
virtual bool IsParredFile(const char* szFilename);
|
||||
virtual EFileStatus FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments);
|
||||
public:
|
||||
PostInfo* GetPostInfo() { return m_pPostInfo; }
|
||||
void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; }
|
||||
time_t GetParTime() { return m_tParTime; }
|
||||
void SetParTime(time_t tParTime) { m_tParTime = tParTime; }
|
||||
time_t GetRepairTime() { return m_tRepairTime; }
|
||||
void SetRepairTime(time_t tRepairTime) { m_tRepairTime = tRepairTime; }
|
||||
int GetDownloadSec() { return m_iDownloadSec; }
|
||||
void SetDownloadSec(int iDownloadSec) { m_iDownloadSec = iDownloadSec; }
|
||||
PostInfo* GetPostInfo() { return m_postInfo; }
|
||||
void SetPostInfo(PostInfo* postInfo) { m_postInfo = postInfo; }
|
||||
time_t GetParTime() { return m_parTime; }
|
||||
void SetParTime(time_t parTime) { m_parTime = parTime; }
|
||||
time_t GetRepairTime() { return m_repairTime; }
|
||||
void SetRepairTime(time_t repairTime) { m_repairTime = repairTime; }
|
||||
int GetDownloadSec() { return m_downloadSec; }
|
||||
void SetDownloadSec(int downloadSec) { m_downloadSec = downloadSec; }
|
||||
protected:
|
||||
virtual bool RequestMorePars(int blockNeeded, int* blockFound);
|
||||
virtual void UpdateProgress();
|
||||
virtual void Completed() { m_owner->ParCheckCompleted(); }
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
|
||||
virtual void RegisterParredFile(const char* filename);
|
||||
virtual bool IsParredFile(const char* filename);
|
||||
virtual EFileStatus FindFileCrc(const char* filename, uint32* crc, SegmentList* segments);
|
||||
virtual void RequestDupeSources(DupeSourceList* dupeSourceList);
|
||||
virtual void StatDupeSources(DupeSourceList* dupeSourceList);
|
||||
private:
|
||||
ParCoordinator* m_owner;
|
||||
PostInfo* m_postInfo;
|
||||
time_t m_parTime;
|
||||
time_t m_repairTime;
|
||||
int m_downloadSec;
|
||||
|
||||
friend class ParCoordinator;
|
||||
};
|
||||
|
||||
class PostParRenamer: public ParRenamer
|
||||
{
|
||||
private:
|
||||
ParCoordinator* m_pOwner;
|
||||
PostInfo* m_pPostInfo;
|
||||
protected:
|
||||
virtual void UpdateProgress();
|
||||
virtual void Completed() { m_pOwner->ParRenameCompleted(); }
|
||||
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...);
|
||||
virtual void RegisterParredFile(const char* szFilename);
|
||||
virtual void RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName);
|
||||
public:
|
||||
PostInfo* GetPostInfo() { return m_pPostInfo; }
|
||||
void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; }
|
||||
|
||||
PostInfo* GetPostInfo() { return m_postInfo; }
|
||||
void SetPostInfo(PostInfo* postInfo) { m_postInfo = postInfo; }
|
||||
protected:
|
||||
virtual void UpdateProgress();
|
||||
virtual void Completed() { m_owner->ParRenameCompleted(); }
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
|
||||
virtual void RegisterParredFile(const char* filename);
|
||||
virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName);
|
||||
private:
|
||||
ParCoordinator* m_owner;
|
||||
PostInfo* m_postInfo;
|
||||
|
||||
friend class ParCoordinator;
|
||||
};
|
||||
|
||||
struct BlockInfo
|
||||
|
||||
class PostDupeMatcher: public DupeMatcher
|
||||
{
|
||||
FileInfo* m_pFileInfo;
|
||||
int m_iBlockCount;
|
||||
public:
|
||||
PostDupeMatcher(PostInfo* postInfo):
|
||||
DupeMatcher(postInfo->GetNzbInfo()->GetDestDir(),
|
||||
postInfo->GetNzbInfo()->GetSize() - postInfo->GetNzbInfo()->GetParSize()),
|
||||
m_postInfo(postInfo) {}
|
||||
protected:
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
|
||||
private:
|
||||
PostInfo* m_postInfo;
|
||||
};
|
||||
|
||||
typedef std::list<BlockInfo*> Blocks;
|
||||
|
||||
struct BlockInfo
|
||||
{
|
||||
FileInfo* m_fileInfo;
|
||||
int m_blockCount;
|
||||
BlockInfo(FileInfo* fileInfo, int blockCount) :
|
||||
m_fileInfo(fileInfo), m_blockCount(blockCount) {}
|
||||
};
|
||||
|
||||
typedef std::deque<BlockInfo> Blocks;
|
||||
|
||||
enum EJobKind
|
||||
{
|
||||
jkParCheck,
|
||||
jkParRename
|
||||
};
|
||||
|
||||
private:
|
||||
PostParChecker m_ParChecker;
|
||||
bool m_bStopped;
|
||||
PostParRenamer m_ParRenamer;
|
||||
EJobKind m_eCurrentJob;
|
||||
PostParChecker m_parChecker;
|
||||
bool m_stopped = false;
|
||||
PostParRenamer m_parRenamer;
|
||||
EJobKind m_currentJob;
|
||||
|
||||
protected:
|
||||
void UpdateParCheckProgress();
|
||||
void UpdateParRenameProgress();
|
||||
void ParCheckCompleted();
|
||||
void ParRenameCompleted();
|
||||
void CheckPauseState(PostInfo* pPostInfo);
|
||||
bool RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound);
|
||||
void PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...);
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef std::deque<char*> ParFileList;
|
||||
|
||||
public:
|
||||
ParCoordinator();
|
||||
virtual ~ParCoordinator();
|
||||
static bool FindMainPars(const char* szPath, ParFileList* pFileList);
|
||||
static bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks);
|
||||
static bool SameParCollection(const char* szFilename1, const char* szFilename2);
|
||||
void PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
bool AddPar(FileInfo* pFileInfo, bool bDeleted);
|
||||
void FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename,
|
||||
Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound);
|
||||
void StartParCheckJob(PostInfo* pPostInfo);
|
||||
void StartParRenameJob(PostInfo* pPostInfo);
|
||||
void Stop();
|
||||
bool Cancel();
|
||||
void FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
|
||||
Blocks& blocks, bool strictParName, bool exactParName, int* blockFound);
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
125
daemon/postprocess/ParParser.cpp
Normal file
125
daemon/postprocess/ParParser.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 "FileSystem.h"
|
||||
#include "ParParser.h"
|
||||
|
||||
bool ParParser::FindMainPars(const char* path, ParFileList* fileList)
|
||||
{
|
||||
if (fileList)
|
||||
{
|
||||
fileList->clear();
|
||||
}
|
||||
|
||||
DirBrowser dir(path);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
int baseLen = 0;
|
||||
if (ParseParFilename(filename, &baseLen, nullptr))
|
||||
{
|
||||
if (!fileList)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the base file already added to list
|
||||
bool exists = false;
|
||||
for (CString& filename2 : fileList)
|
||||
{
|
||||
exists = SameParCollection(filename, filename2);
|
||||
if (exists)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists)
|
||||
{
|
||||
fileList->emplace_back(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileList && !fileList->empty();
|
||||
}
|
||||
|
||||
bool ParParser::SameParCollection(const char* filename1, const char* filename2)
|
||||
{
|
||||
int baseLen1 = 0, baseLen2 = 0;
|
||||
return ParseParFilename(filename1, &baseLen1, nullptr) &&
|
||||
ParseParFilename(filename2, &baseLen2, nullptr) &&
|
||||
baseLen1 == baseLen2 &&
|
||||
!strncasecmp(filename1, filename2, baseLen1);
|
||||
}
|
||||
|
||||
bool ParParser::ParseParFilename(const char* parFilename, int* baseNameLen, int* blocks)
|
||||
{
|
||||
BString<1024> filename = parFilename;
|
||||
for (char* p = filename; *p; p++) *p = tolower(*p); // convert string to lowercase
|
||||
|
||||
int len = strlen(filename);
|
||||
if (len < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// find last occurence of ".par2" and trim filename after it
|
||||
char* end = filename;
|
||||
while (char* p = strstr(end, ".par2")) end = p + 5;
|
||||
*end = '\0';
|
||||
|
||||
len = strlen(filename);
|
||||
if (len < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcasecmp(filename + len - 5, ".par2"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*(filename + len - 5) = '\0';
|
||||
|
||||
int blockcnt = 0;
|
||||
char* p = strrchr(filename, '.');
|
||||
if (p && !strncasecmp(p, ".vol", 4))
|
||||
{
|
||||
char* b = strchr(p, '+');
|
||||
if (!b)
|
||||
{
|
||||
b = strchr(p, '-');
|
||||
}
|
||||
if (b)
|
||||
{
|
||||
blockcnt = atoi(b+1);
|
||||
*p = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (baseNameLen)
|
||||
{
|
||||
*baseNameLen = strlen(filename);
|
||||
}
|
||||
if (blocks)
|
||||
{
|
||||
*blocks = blockcnt;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
37
daemon/postprocess/ParParser.h
Normal file
37
daemon/postprocess/ParParser.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 PARPARSER_H
|
||||
#define PARPARSER_H
|
||||
|
||||
#include "NString.h"
|
||||
#include "Container.h"
|
||||
|
||||
class ParParser
|
||||
{
|
||||
public:
|
||||
typedef std::vector<CString> ParFileList;
|
||||
|
||||
static bool FindMainPars(const char* path, ParFileList* fileList);
|
||||
static bool ParseParFilename(const char* parFilename, int* baseNameLen, int* blocks);
|
||||
static bool SameParCollection(const char* filename1, const char* filename2);
|
||||
};
|
||||
|
||||
#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,291 +14,186 @@
|
||||
* 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 "nzbget.h"
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "par2cmdline.h"
|
||||
#include "par2repairer.h"
|
||||
#include "md5.h"
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "ParRenamer.h"
|
||||
#include "ParCoordinator.h"
|
||||
#include "ParParser.h"
|
||||
#include "Log.h"
|
||||
#include "Options.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
|
||||
class ParRenamerRepairer : public Par2Repairer
|
||||
class ParRenamerRepairer : public Par2::Par2Repairer
|
||||
{
|
||||
public:
|
||||
friend class ParRenamer;
|
||||
};
|
||||
|
||||
ParRenamer::FileHash::FileHash(const char* szFilename, const char* szHash)
|
||||
{
|
||||
m_szFilename = strdup(szFilename);
|
||||
m_szHash = strdup(szHash);
|
||||
m_bFileExists = false;
|
||||
}
|
||||
|
||||
ParRenamer::FileHash::~FileHash()
|
||||
{
|
||||
free(m_szFilename);
|
||||
free(m_szHash);
|
||||
}
|
||||
|
||||
ParRenamer::ParRenamer()
|
||||
{
|
||||
debug("Creating ParRenamer");
|
||||
|
||||
m_eStatus = psFailed;
|
||||
m_szDestDir = NULL;
|
||||
m_szInfoName = NULL;
|
||||
m_szProgressLabel = (char*)malloc(1024);
|
||||
m_iStageProgress = 0;
|
||||
m_bCancelled = false;
|
||||
m_bHasMissedFiles = false;
|
||||
m_bDetectMissing = false;
|
||||
}
|
||||
|
||||
ParRenamer::~ParRenamer()
|
||||
{
|
||||
debug("Destroying ParRenamer");
|
||||
|
||||
free(m_szDestDir);
|
||||
free(m_szInfoName);
|
||||
free(m_szProgressLabel);
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void ParRenamer::Cleanup()
|
||||
{
|
||||
ClearHashList();
|
||||
|
||||
for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++)
|
||||
{
|
||||
free(*it);
|
||||
}
|
||||
m_DirList.clear();
|
||||
}
|
||||
|
||||
void ParRenamer::ClearHashList()
|
||||
{
|
||||
for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++)
|
||||
{
|
||||
delete *it;
|
||||
}
|
||||
m_FileHashList.clear();
|
||||
}
|
||||
|
||||
void ParRenamer::SetDestDir(const char * szDestDir)
|
||||
{
|
||||
free(m_szDestDir);
|
||||
m_szDestDir = strdup(szDestDir);
|
||||
}
|
||||
|
||||
void ParRenamer::SetInfoName(const char * szInfoName)
|
||||
{
|
||||
free(m_szInfoName);
|
||||
m_szInfoName = strdup(szInfoName);
|
||||
m_dirList.clear();
|
||||
m_fileHashList.clear();
|
||||
}
|
||||
|
||||
void ParRenamer::Cancel()
|
||||
{
|
||||
m_bCancelled = true;
|
||||
m_cancelled = true;
|
||||
}
|
||||
|
||||
void ParRenamer::Run()
|
||||
{
|
||||
Cleanup();
|
||||
m_bCancelled = false;
|
||||
m_iFileCount = 0;
|
||||
m_iCurFile = 0;
|
||||
m_iRenamedCount = 0;
|
||||
m_bHasMissedFiles = false;
|
||||
m_eStatus = psFailed;
|
||||
m_cancelled = false;
|
||||
m_fileCount = 0;
|
||||
m_curFile = 0;
|
||||
m_renamedCount = 0;
|
||||
m_hasMissedFiles = false;
|
||||
m_status = psFailed;
|
||||
|
||||
snprintf(m_szProgressLabel, 1024, "Checking renamed files for %s", m_szInfoName);
|
||||
m_szProgressLabel[1024-1] = '\0';
|
||||
m_iStageProgress = 0;
|
||||
m_progressLabel.Format("Checking renamed files for %s", *m_infoName);
|
||||
m_stageProgress = 0;
|
||||
UpdateProgress();
|
||||
|
||||
BuildDirList(m_szDestDir);
|
||||
BuildDirList(m_destDir);
|
||||
|
||||
for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++)
|
||||
for (CString& destDir : m_dirList)
|
||||
{
|
||||
char* szDestDir = *it;
|
||||
debug("Checking %s", szDestDir);
|
||||
ClearHashList();
|
||||
LoadParFiles(szDestDir);
|
||||
debug("Checking %s", *destDir);
|
||||
m_fileHashList.clear();
|
||||
LoadParFiles(destDir);
|
||||
|
||||
if (m_FileHashList.empty())
|
||||
if (m_fileHashList.empty())
|
||||
{
|
||||
int iSavedCurFile = m_iCurFile;
|
||||
CheckFiles(szDestDir, true);
|
||||
m_iCurFile = iSavedCurFile; // restore progress indicator
|
||||
LoadParFiles(szDestDir);
|
||||
int savedCurFile = m_curFile;
|
||||
CheckFiles(destDir, true);
|
||||
m_curFile = savedCurFile; // restore progress indicator
|
||||
LoadParFiles(destDir);
|
||||
}
|
||||
|
||||
CheckFiles(szDestDir, false);
|
||||
CheckFiles(destDir, false);
|
||||
|
||||
if (m_bDetectMissing)
|
||||
if (m_detectMissing)
|
||||
{
|
||||
CheckMissing();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bCancelled)
|
||||
if (m_cancelled)
|
||||
{
|
||||
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", m_szInfoName);
|
||||
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", *m_infoName);
|
||||
}
|
||||
else if (m_iRenamedCount > 0)
|
||||
else if (m_renamedCount > 0)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_iRenamedCount, m_szInfoName);
|
||||
m_eStatus = psSuccess;
|
||||
PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_renamedCount, *m_infoName);
|
||||
m_status = psSuccess;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "No renamed files found for %s", m_szInfoName);
|
||||
PrintMessage(Message::mkInfo, "No renamed files found for %s", *m_infoName);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
Completed();
|
||||
}
|
||||
|
||||
void ParRenamer::BuildDirList(const char* szDestDir)
|
||||
void ParRenamer::BuildDirList(const char* destDir)
|
||||
{
|
||||
m_DirList.push_back(strdup(szDestDir));
|
||||
m_dirList.push_back(destDir);
|
||||
|
||||
char* szFullFilename = (char*)malloc(1024);
|
||||
DirBrowser* pDirBrowser = new DirBrowser(szDestDir);
|
||||
|
||||
while (const char* filename = pDirBrowser->Next())
|
||||
DirBrowser dirBrowser(destDir);
|
||||
|
||||
while (const char* filename = dirBrowser.Next())
|
||||
{
|
||||
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled)
|
||||
if (!m_cancelled)
|
||||
{
|
||||
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
|
||||
szFullFilename[1024-1] = '\0';
|
||||
|
||||
if (Util::DirectoryExists(szFullFilename))
|
||||
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
|
||||
if (FileSystem::DirectoryExists(fullFilename))
|
||||
{
|
||||
BuildDirList(szFullFilename);
|
||||
BuildDirList(fullFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iFileCount++;
|
||||
m_fileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(szFullFilename);
|
||||
delete pDirBrowser;
|
||||
}
|
||||
|
||||
void ParRenamer::LoadParFiles(const char* szDestDir)
|
||||
void ParRenamer::LoadParFiles(const char* destDir)
|
||||
{
|
||||
ParCoordinator::ParFileList parFileList;
|
||||
ParCoordinator::FindMainPars(szDestDir, &parFileList);
|
||||
ParParser::ParFileList parFileList;
|
||||
ParParser::FindMainPars(destDir, &parFileList);
|
||||
|
||||
for (ParCoordinator::ParFileList::iterator it = parFileList.begin(); it != parFileList.end(); it++)
|
||||
for (CString& parFilename : parFileList)
|
||||
{
|
||||
char* szParFilename = *it;
|
||||
|
||||
char szFullParFilename[1024];
|
||||
snprintf(szFullParFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, szParFilename);
|
||||
szFullParFilename[1024-1] = '\0';
|
||||
|
||||
LoadParFile(szFullParFilename);
|
||||
|
||||
free(*it);
|
||||
BString<1024> fullParFilename("%s%c%s", destDir, PATH_SEPARATOR, *parFilename);
|
||||
LoadParFile(fullParFilename);
|
||||
}
|
||||
}
|
||||
|
||||
void ParRenamer::LoadParFile(const char* szParFilename)
|
||||
void ParRenamer::LoadParFile(const char* parFilename)
|
||||
{
|
||||
ParRenamerRepairer* pRepairer = new ParRenamerRepairer();
|
||||
ParRenamerRepairer repairer;
|
||||
|
||||
if (!pRepairer->LoadPacketsFromFile(szParFilename))
|
||||
if (!repairer.LoadPacketsFromFile(parFilename))
|
||||
{
|
||||
PrintMessage(Message::mkWarning, "Could not load par2-file %s", szParFilename);
|
||||
delete pRepairer;
|
||||
PrintMessage(Message::mkWarning, "Could not load par2-file %s", parFilename);
|
||||
return;
|
||||
}
|
||||
|
||||
for (map<MD5Hash, Par2RepairerSourceFile*>::iterator it = pRepairer->sourcefilemap.begin(); it != pRepairer->sourcefilemap.end(); it++)
|
||||
for (std::pair<const Par2::MD5Hash, Par2::Par2RepairerSourceFile*>& entry : repairer.sourcefilemap)
|
||||
{
|
||||
if (m_bCancelled)
|
||||
if (m_cancelled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Par2RepairerSourceFile* sourceFile = (*it).second;
|
||||
|
||||
Par2::Par2RepairerSourceFile* sourceFile = entry.second;
|
||||
if (!sourceFile || !sourceFile->GetDescriptionPacket())
|
||||
{
|
||||
warn("Damaged par2-file detected: %s", szParFilename);
|
||||
PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", parFilename);
|
||||
continue;
|
||||
}
|
||||
m_FileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(),
|
||||
sourceFile->GetDescriptionPacket()->Hash16k().print().c_str()));
|
||||
RegisterParredFile(sourceFile->GetDescriptionPacket()->FileName().c_str());
|
||||
std::string filename = Par2::DiskFile::TranslateFilename(sourceFile->GetDescriptionPacket()->FileName());
|
||||
m_fileHashList.emplace_back(filename.c_str(), sourceFile->GetDescriptionPacket()->Hash16k().print().c_str());
|
||||
RegisterParredFile(filename.c_str());
|
||||
}
|
||||
|
||||
delete pRepairer;
|
||||
}
|
||||
|
||||
void ParRenamer::CheckFiles(const char* szDestDir, bool bRenamePars)
|
||||
void ParRenamer::CheckFiles(const char* destDir, bool renamePars)
|
||||
{
|
||||
DirBrowser dir(szDestDir);
|
||||
DirBrowser dir(destDir);
|
||||
while (const char* filename = dir.Next())
|
||||
{
|
||||
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled)
|
||||
if (!m_cancelled)
|
||||
{
|
||||
char szFullFilename[1024];
|
||||
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
|
||||
szFullFilename[1024-1] = '\0';
|
||||
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
|
||||
|
||||
if (!Util::DirectoryExists(szFullFilename))
|
||||
if (!FileSystem::DirectoryExists(fullFilename))
|
||||
{
|
||||
snprintf(m_szProgressLabel, 1024, "Checking file %s", filename);
|
||||
m_szProgressLabel[1024-1] = '\0';
|
||||
m_iStageProgress = m_iCurFile * 1000 / m_iFileCount;
|
||||
m_progressLabel.Format("Checking file %s", filename);
|
||||
m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
|
||||
UpdateProgress();
|
||||
m_iCurFile++;
|
||||
|
||||
if (bRenamePars)
|
||||
m_curFile++;
|
||||
|
||||
if (renamePars)
|
||||
{
|
||||
CheckParFile(szDestDir, szFullFilename);
|
||||
CheckParFile(destDir, fullFilename);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckRegularFile(szDestDir, szFullFilename);
|
||||
CheckRegularFile(destDir, fullFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,98 +202,88 @@ void ParRenamer::CheckFiles(const char* szDestDir, bool bRenamePars)
|
||||
|
||||
void ParRenamer::CheckMissing()
|
||||
{
|
||||
for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++)
|
||||
for (FileHash& fileHash : m_fileHashList)
|
||||
{
|
||||
FileHash* pFileHash = *it;
|
||||
if (!pFileHash->GetFileExists())
|
||||
if (!fileHash.GetFileExists())
|
||||
{
|
||||
if (Util::MatchFileExt(pFileHash->GetFilename(), g_pOptions->GetParIgnoreExt(), ",;") ||
|
||||
Util::MatchFileExt(pFileHash->GetFilename(), g_pOptions->GetExtCleanupDisk(), ",;"))
|
||||
if (Util::MatchFileExt(fileHash.GetFilename(), g_Options->GetParIgnoreExt(), ",;"))
|
||||
{
|
||||
info("File %s is missing, ignoring", pFileHash->GetFilename());
|
||||
PrintMessage(Message::mkInfo, "File %s is missing, ignoring", fileHash.GetFilename());
|
||||
}
|
||||
else
|
||||
{
|
||||
info("File %s is missing", pFileHash->GetFilename());
|
||||
m_bHasMissedFiles = true;
|
||||
PrintMessage(Message::mkInfo, "File %s is missing", fileHash.GetFilename());
|
||||
m_hasMissedFiles = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParRenamer::IsSplittedFragment(const char* szFilename, const char* szCorrectName)
|
||||
bool ParRenamer::IsSplittedFragment(const char* filename, const char* correctName)
|
||||
{
|
||||
bool bSplittedFragement = false;
|
||||
const char* szDiskBasename = Util::BaseFileName(szFilename);
|
||||
const char* szExtension = strrchr(szDiskBasename, '.');
|
||||
int iBaseLen = strlen(szCorrectName);
|
||||
if (szExtension && !strncasecmp(szDiskBasename, szCorrectName, iBaseLen))
|
||||
bool splittedFragement = false;
|
||||
const char* diskBasename = FileSystem::BaseFileName(filename);
|
||||
const char* extension = strrchr(diskBasename, '.');
|
||||
int baseLen = strlen(correctName);
|
||||
if (extension && !strncasecmp(diskBasename, correctName, baseLen))
|
||||
{
|
||||
const char* p = szDiskBasename + iBaseLen;
|
||||
const char* p = diskBasename + baseLen;
|
||||
if (*p == '.')
|
||||
{
|
||||
for (p++; *p && strchr("0123456789", *p); p++) ;
|
||||
bSplittedFragement = !*p;
|
||||
bSplittedFragement = bSplittedFragement && atoi(szDiskBasename + iBaseLen + 1) <= 1; // .000 or .001
|
||||
splittedFragement = !*p;
|
||||
splittedFragement = splittedFragement && atoi(diskBasename + baseLen + 1) <= 1; // .000 or .001
|
||||
}
|
||||
}
|
||||
|
||||
return bSplittedFragement;
|
||||
return splittedFragement;
|
||||
}
|
||||
|
||||
void ParRenamer::CheckRegularFile(const char* szDestDir, const char* szFilename)
|
||||
void ParRenamer::CheckRegularFile(const char* destDir, const char* filename)
|
||||
{
|
||||
debug("Computing hash for %s", szFilename);
|
||||
debug("Computing hash for %s", filename);
|
||||
|
||||
const int iBlockSize = 16*1024;
|
||||
|
||||
FILE* pFile = fopen(szFilename, FOPEN_RB);
|
||||
if (!pFile)
|
||||
DiskFile file;
|
||||
if (!file.Open(filename, DiskFile::omRead))
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not open file %s", szFilename);
|
||||
PrintMessage(Message::mkError, "Could not open file %s", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// load first 16K of the file into buffer
|
||||
|
||||
void* pBuffer = malloc(iBlockSize);
|
||||
|
||||
int iReadBytes = fread(pBuffer, 1, iBlockSize, pFile);
|
||||
int iError = ferror(pFile);
|
||||
if (iReadBytes != iBlockSize && iError)
|
||||
static const int blockSize = 16*1024;
|
||||
CharBuffer buffer(blockSize);
|
||||
|
||||
int readBytes = (int)file.Read(buffer, buffer.Size());
|
||||
if (readBytes != buffer.Size() && file.Error())
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not read file %s", szFilename);
|
||||
PrintMessage(Message::mkError, "Could not read file %s", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(pFile);
|
||||
|
||||
MD5Hash hash16k;
|
||||
MD5Context context;
|
||||
context.Update(pBuffer, iReadBytes);
|
||||
|
||||
file.Close();
|
||||
|
||||
Par2::MD5Hash hash16k;
|
||||
Par2::MD5Context context;
|
||||
context.Update(buffer, readBytes);
|
||||
context.Final(hash16k);
|
||||
|
||||
free(pBuffer);
|
||||
|
||||
debug("file: %s; hash16k: %s", Util::BaseFileName(szFilename), hash16k.print().c_str());
|
||||
|
||||
for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++)
|
||||
|
||||
debug("file: %s; hash16k: %s", FileSystem::BaseFileName(filename), hash16k.print().c_str());
|
||||
|
||||
for (FileHash& fileHash : m_fileHashList)
|
||||
{
|
||||
FileHash* pFileHash = *it;
|
||||
if (!strcmp(pFileHash->GetHash(), hash16k.print().c_str()))
|
||||
if (!strcmp(fileHash.GetHash(), hash16k.print().c_str()))
|
||||
{
|
||||
debug("Found correct filename: %s", pFileHash->GetFilename());
|
||||
pFileHash->SetFileExists(true);
|
||||
debug("Found correct filename: %s", fileHash.GetFilename());
|
||||
fileHash.SetFileExists(true);
|
||||
|
||||
char szDstFilename[1024];
|
||||
snprintf(szDstFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, pFileHash->GetFilename());
|
||||
szDstFilename[1024-1] = '\0';
|
||||
BString<1024> dstFilename("%s%c%s", destDir, PATH_SEPARATOR, fileHash.GetFilename());
|
||||
|
||||
if (!Util::FileExists(szDstFilename) && !IsSplittedFragment(szFilename, pFileHash->GetFilename()))
|
||||
if (!FileSystem::FileExists(dstFilename) && !IsSplittedFragment(filename, fileHash.GetFilename()))
|
||||
{
|
||||
RenameFile(szFilename, szDstFilename);
|
||||
RenameFile(filename, dstFilename);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -408,82 +293,77 @@ void ParRenamer::CheckRegularFile(const char* szDestDir, const char* szFilename)
|
||||
* For files not having par2-extensions: checks if the file is a par2-file and renames
|
||||
* it according to its set-id.
|
||||
*/
|
||||
void ParRenamer::CheckParFile(const char* szDestDir, const char* szFilename)
|
||||
void ParRenamer::CheckParFile(const char* destDir, const char* filename)
|
||||
{
|
||||
debug("Checking par2-header for %s", szFilename);
|
||||
debug("Checking par2-header for %s", filename);
|
||||
|
||||
const char* szBasename = Util::BaseFileName(szFilename);
|
||||
const char* szExtension = strrchr(szBasename, '.');
|
||||
if (szExtension && !strcasecmp(szExtension, ".par2"))
|
||||
const char* basename = FileSystem::BaseFileName(filename);
|
||||
const char* extension = strrchr(basename, '.');
|
||||
if (extension && !strcasecmp(extension, ".par2"))
|
||||
{
|
||||
// do not process files already having par2-extension
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* pFile = fopen(szFilename, FOPEN_RB);
|
||||
if (!pFile)
|
||||
DiskFile file;
|
||||
if (!file.Open(filename, DiskFile::omRead))
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not open file %s", szFilename);
|
||||
PrintMessage(Message::mkError, "Could not open file %s", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// load par2-header
|
||||
PACKET_HEADER header;
|
||||
Par2::PACKET_HEADER header;
|
||||
|
||||
int iReadBytes = fread(&header, 1, sizeof(header), pFile);
|
||||
int iError = ferror(pFile);
|
||||
if (iReadBytes != sizeof(header) && iError)
|
||||
int readBytes = (int)file.Read(&header, sizeof(header));
|
||||
if (readBytes != sizeof(header) && file.Error())
|
||||
{
|
||||
PrintMessage(Message::mkError, "Could not read file %s", szFilename);
|
||||
PrintMessage(Message::mkError, "Could not read file %s", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(pFile);
|
||||
file.Close();
|
||||
|
||||
// Check the packet header
|
||||
if (packet_magic != header.magic || // not par2-file
|
||||
sizeof(PACKET_HEADER) > header.length || // packet length is too small
|
||||
if (Par2::packet_magic != header.magic || // not par2-file
|
||||
sizeof(Par2::PACKET_HEADER) > header.length || // packet length is too small
|
||||
0 != (header.length & 3) || // packet length is not a multiple of 4
|
||||
Util::FileSize(szFilename) < (int)header.length) // packet would extend beyond the end of the file
|
||||
FileSystem::FileSize(filename) < (int)header.length) // packet would extend beyond the end of the file
|
||||
{
|
||||
// not par2-file or damaged header, ignoring the file
|
||||
return;
|
||||
}
|
||||
|
||||
char szSetId[33];
|
||||
strncpy(szSetId, header.setid.print().c_str(), sizeof(szSetId));
|
||||
szSetId[33-1] = '\0';
|
||||
for (char* p = szSetId; *p; p++) *p = tolower(*p); // convert string to lowercase
|
||||
BString<100> setId = header.setid.print().c_str();
|
||||
for (char* p = setId; *p; p++) *p = tolower(*p); // convert string to lowercase
|
||||
|
||||
debug("Renaming: %s; setid: %s", Util::BaseFileName(szFilename), szSetId);
|
||||
debug("Renaming: %s; setid: %s", FileSystem::BaseFileName(filename), *setId);
|
||||
|
||||
char szDestFileName[1024];
|
||||
int iNum = 1;
|
||||
while (iNum == 1 || Util::FileExists(szDestFileName))
|
||||
BString<1024> destFileName;
|
||||
int num = 1;
|
||||
while (num == 1 || FileSystem::FileExists(destFileName))
|
||||
{
|
||||
snprintf(szDestFileName, 1024, "%s%c%s.vol%03i+01.PAR2", szDestDir, PATH_SEPARATOR, szSetId, iNum);
|
||||
szDestFileName[1024-1] = '\0';
|
||||
iNum++;
|
||||
destFileName.Format("%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, *setId, num);
|
||||
num++;
|
||||
}
|
||||
|
||||
RenameFile(szFilename, szDestFileName);
|
||||
RenameFile(filename, destFileName);
|
||||
}
|
||||
|
||||
void ParRenamer::RenameFile(const char* szSrcFilename, const char* szDestFileName)
|
||||
void ParRenamer::RenameFile(const char* srcFilename, const char* destFileName)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Renaming %s to %s", Util::BaseFileName(szSrcFilename), Util::BaseFileName(szDestFileName));
|
||||
if (!Util::MoveFile(szSrcFilename, szDestFileName))
|
||||
PrintMessage(Message::mkInfo, "Renaming %s to %s", FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
|
||||
if (!FileSystem::MoveFile(srcFilename, destFileName))
|
||||
{
|
||||
char szErrBuf[256];
|
||||
PrintMessage(Message::mkError, "Could not rename %s to %s: %s", szSrcFilename, szDestFileName,
|
||||
Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)));
|
||||
PrintMessage(Message::mkError, "Could not rename %s to %s: %s", srcFilename, destFileName,
|
||||
*FileSystem::GetLastErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
m_iRenamedCount++;
|
||||
m_renamedCount++;
|
||||
|
||||
// notify about new file name
|
||||
RegisterRenamedFile(Util::BaseFileName(szSrcFilename), Util::BaseFileName(szDestFileName));
|
||||
RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
|
||||
}
|
||||
|
||||
#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,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/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -28,8 +23,7 @@
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "NString.h"
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
|
||||
@@ -41,76 +35,71 @@ public:
|
||||
psFailed,
|
||||
psSuccess
|
||||
};
|
||||
|
||||
class FileHash
|
||||
{
|
||||
private:
|
||||
char* m_szFilename;
|
||||
char* m_szHash;
|
||||
bool m_bFileExists;
|
||||
|
||||
public:
|
||||
FileHash(const char* szFilename, const char* szHash);
|
||||
~FileHash();
|
||||
const char* GetFilename() { return m_szFilename; }
|
||||
const char* GetHash() { return m_szHash; }
|
||||
bool GetFileExists() { return m_bFileExists; }
|
||||
void SetFileExists(bool bFileExists) { m_bFileExists = bFileExists; }
|
||||
};
|
||||
|
||||
typedef std::deque<FileHash*> FileHashList;
|
||||
typedef std::deque<char*> DirList;
|
||||
|
||||
private:
|
||||
char* m_szInfoName;
|
||||
char* m_szDestDir;
|
||||
EStatus m_eStatus;
|
||||
char* m_szProgressLabel;
|
||||
int m_iStageProgress;
|
||||
bool m_bCancelled;
|
||||
DirList m_DirList;
|
||||
FileHashList m_FileHashList;
|
||||
int m_iFileCount;
|
||||
int m_iCurFile;
|
||||
int m_iRenamedCount;
|
||||
bool m_bHasMissedFiles;
|
||||
bool m_bDetectMissing;
|
||||
|
||||
void Cleanup();
|
||||
void ClearHashList();
|
||||
void BuildDirList(const char* szDestDir);
|
||||
void CheckDir(const char* szDestDir);
|
||||
void LoadParFiles(const char* szDestDir);
|
||||
void LoadParFile(const char* szParFilename);
|
||||
void CheckFiles(const char* szDestDir, bool bRenamePars);
|
||||
void CheckRegularFile(const char* szDestDir, const char* szFilename);
|
||||
void CheckParFile(const char* szDestDir, const char* szFilename);
|
||||
bool IsSplittedFragment(const char* szFilename, const char* szCorrectName);
|
||||
void CheckMissing();
|
||||
void RenameFile(const char* szSrcFilename, const char* szDestFileName);
|
||||
virtual void Run();
|
||||
void SetDestDir(const char* destDir) { m_destDir = destDir; }
|
||||
const char* GetInfoName() { return m_infoName; }
|
||||
void SetInfoName(const char* infoName) { m_infoName = infoName; }
|
||||
void SetStatus(EStatus status);
|
||||
EStatus GetStatus() { return m_status; }
|
||||
void Cancel();
|
||||
bool GetCancelled() { return m_cancelled; }
|
||||
bool HasMissedFiles() { return m_hasMissedFiles; }
|
||||
void SetDetectMissing(bool detectMissing) { m_detectMissing = detectMissing; }
|
||||
|
||||
protected:
|
||||
virtual void UpdateProgress() {}
|
||||
virtual void Completed() {}
|
||||
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {}
|
||||
virtual void RegisterParredFile(const char* szFilename) {}
|
||||
virtual void RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName) {}
|
||||
const char* GetProgressLabel() { return m_szProgressLabel; }
|
||||
int GetStageProgress() { return m_iStageProgress; }
|
||||
virtual void UpdateProgress() {}
|
||||
virtual void Completed() {}
|
||||
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
|
||||
virtual void RegisterParredFile(const char* filename) {}
|
||||
virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName) {}
|
||||
const char* GetProgressLabel() { return m_progressLabel; }
|
||||
int GetStageProgress() { return m_stageProgress; }
|
||||
|
||||
public:
|
||||
ParRenamer();
|
||||
virtual ~ParRenamer();
|
||||
virtual void Run();
|
||||
void SetDestDir(const char* szDestDir);
|
||||
const char* GetInfoName() { return m_szInfoName; }
|
||||
void SetInfoName(const char* szInfoName);
|
||||
void SetStatus(EStatus eStatus);
|
||||
EStatus GetStatus() { return m_eStatus; }
|
||||
void Cancel();
|
||||
bool GetCancelled() { return m_bCancelled; }
|
||||
bool HasMissedFiles() { return m_bHasMissedFiles; }
|
||||
void SetDetectMissing(bool bDetectMissing) { m_bDetectMissing = bDetectMissing; }
|
||||
private:
|
||||
class FileHash
|
||||
{
|
||||
public:
|
||||
FileHash(const char* filename, const char* hash) :
|
||||
m_filename(filename), m_hash(hash) {}
|
||||
const char* GetFilename() { return m_filename; }
|
||||
const char* GetHash() { return m_hash; }
|
||||
bool GetFileExists() { return m_fileExists; }
|
||||
void SetFileExists(bool fileExists) { m_fileExists = fileExists; }
|
||||
private:
|
||||
CString m_filename;
|
||||
CString m_hash;
|
||||
bool m_fileExists = false;
|
||||
};
|
||||
|
||||
typedef std::deque<FileHash> FileHashList;
|
||||
typedef std::deque<CString> DirList;
|
||||
|
||||
CString m_infoName;
|
||||
CString m_destDir;
|
||||
EStatus m_status;
|
||||
CString m_progressLabel;
|
||||
int m_stageProgress;
|
||||
bool m_cancelled;
|
||||
DirList m_dirList;
|
||||
FileHashList m_fileHashList;
|
||||
int m_fileCount;
|
||||
int m_curFile;
|
||||
int m_renamedCount;
|
||||
bool m_hasMissedFiles;
|
||||
bool m_detectMissing = false;
|
||||
|
||||
void BuildDirList(const char* destDir);
|
||||
void CheckDir(const char* destDir);
|
||||
void LoadParFiles(const char* destDir);
|
||||
void LoadParFile(const char* parFilename);
|
||||
void CheckFiles(const char* destDir, bool renamePars);
|
||||
void CheckRegularFile(const char* destDir, const char* filename);
|
||||
void CheckParFile(const char* destDir, const char* filename);
|
||||
bool IsSplittedFragment(const char* filename, const char* correctName);
|
||||
void CheckMissing();
|
||||
void RenameFile(const char* srcFilename, const char* destFileName);
|
||||
void Cleanup();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2007-2014 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 <ctype.h>
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "PostScript.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "Options.h"
|
||||
|
||||
extern Options* g_pOptions;
|
||||
|
||||
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* pPostInfo)
|
||||
{
|
||||
PostScriptController* pScriptController = new PostScriptController();
|
||||
pScriptController->m_pPostInfo = pPostInfo;
|
||||
pScriptController->SetWorkingDir(g_pOptions->GetDestDir());
|
||||
pScriptController->SetAutoDestroy(false);
|
||||
pScriptController->m_iPrefixLen = 0;
|
||||
|
||||
pPostInfo->SetPostThread(pScriptController);
|
||||
|
||||
pScriptController->Start();
|
||||
}
|
||||
|
||||
void PostScriptController::Run()
|
||||
{
|
||||
StringBuilder scriptCommaList;
|
||||
|
||||
// the locking is needed for accessing the members of NZBInfo
|
||||
DownloadQueue::Lock();
|
||||
for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++)
|
||||
{
|
||||
NZBParameter* pParameter = *it;
|
||||
const char* szVarname = pParameter->GetName();
|
||||
if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' &&
|
||||
(!strcasecmp(pParameter->GetValue(), "yes") || !strcasecmp(pParameter->GetValue(), "on") || !strcasecmp(pParameter->GetValue(), "1")))
|
||||
{
|
||||
char* szScriptName = strdup(szVarname);
|
||||
szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':'
|
||||
scriptCommaList.Append(szScriptName);
|
||||
scriptCommaList.Append(",");
|
||||
free(szScriptName);
|
||||
}
|
||||
}
|
||||
m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear();
|
||||
DownloadQueue::Unlock();
|
||||
|
||||
ExecuteScriptList(scriptCommaList.GetBuffer());
|
||||
|
||||
m_pPostInfo->SetStage(PostInfo::ptFinished);
|
||||
m_pPostInfo->SetWorking(false);
|
||||
}
|
||||
|
||||
void PostScriptController::ExecuteScript(Options::Script* pScript)
|
||||
{
|
||||
// if any script has requested par-check, do not execute other scripts
|
||||
if (!pScript->GetPostScript() || m_pPostInfo->GetRequestParCheck())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName());
|
||||
|
||||
SetScript(pScript->GetLocation());
|
||||
SetArgs(NULL, false);
|
||||
|
||||
char szInfoName[1024];
|
||||
snprintf(szInfoName, 1024, "post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName());
|
||||
szInfoName[1024-1] = '\0';
|
||||
SetInfoName(szInfoName);
|
||||
|
||||
m_pScript = pScript;
|
||||
SetLogPrefix(pScript->GetDisplayName());
|
||||
m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": ");
|
||||
PrepareParams(pScript->GetName());
|
||||
|
||||
int iExitCode = Execute();
|
||||
|
||||
szInfoName[0] = 'P'; // uppercase
|
||||
|
||||
SetLogPrefix(NULL);
|
||||
ScriptStatus::EStatus eStatus = AnalyseExitCode(iExitCode);
|
||||
|
||||
// the locking is needed for accessing the members of NZBInfo
|
||||
DownloadQueue::Lock();
|
||||
m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Add(pScript->GetName(), eStatus);
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
|
||||
void PostScriptController::PrepareParams(const char* szScriptName)
|
||||
{
|
||||
// the locking is needed for accessing the members of NZBInfo
|
||||
DownloadQueue::Lock();
|
||||
|
||||
ResetEnv();
|
||||
|
||||
SetIntEnvVar("NZBPP_NZBID", m_pPostInfo->GetNZBInfo()->GetID());
|
||||
SetEnvVar("NZBPP_NZBNAME", m_pPostInfo->GetNZBInfo()->GetName());
|
||||
SetEnvVar("NZBPP_DIRECTORY", m_pPostInfo->GetNZBInfo()->GetDestDir());
|
||||
SetEnvVar("NZBPP_NZBFILENAME", m_pPostInfo->GetNZBInfo()->GetFilename());
|
||||
SetEnvVar("NZBPP_URL", m_pPostInfo->GetNZBInfo()->GetURL());
|
||||
SetEnvVar("NZBPP_FINALDIR", m_pPostInfo->GetNZBInfo()->GetFinalDir());
|
||||
SetEnvVar("NZBPP_CATEGORY", m_pPostInfo->GetNZBInfo()->GetCategory());
|
||||
SetIntEnvVar("NZBPP_HEALTH", m_pPostInfo->GetNZBInfo()->CalcHealth());
|
||||
SetIntEnvVar("NZBPP_CRITICALHEALTH", m_pPostInfo->GetNZBInfo()->CalcCriticalHealth(false));
|
||||
|
||||
char szStatus[256];
|
||||
strncpy(szStatus, m_pPostInfo->GetNZBInfo()->MakeTextStatus(true), sizeof(szStatus));
|
||||
szStatus[256-1] = '\0';
|
||||
SetEnvVar("NZBPP_STATUS", szStatus);
|
||||
|
||||
char* szDetail = strchr(szStatus, '/');
|
||||
if (szDetail) *szDetail = '\0';
|
||||
SetEnvVar("NZBPP_TOTALSTATUS", szStatus);
|
||||
|
||||
const char* szScriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" };
|
||||
SetEnvVar("NZBPP_SCRIPTSTATUS", szScriptStatusName[m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->CalcTotalStatus()]);
|
||||
|
||||
// deprecated
|
||||
int iParStatus[] = { 0, 0, 1, 2, 3, 4 };
|
||||
NZBInfo::EParStatus eParStatus = m_pPostInfo->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_pPostInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone ||
|
||||
m_pPostInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksBad)
|
||||
{
|
||||
eParStatus = NZBInfo::psFailure;
|
||||
}
|
||||
SetIntEnvVar("NZBPP_PARSTATUS", iParStatus[eParStatus]);
|
||||
|
||||
// deprecated
|
||||
int iUnpackStatus[] = { 0, 0, 1, 2, 3, 4 };
|
||||
SetIntEnvVar("NZBPP_UNPACKSTATUS", iUnpackStatus[m_pPostInfo->GetNZBInfo()->GetUnpackStatus()]);
|
||||
|
||||
// deprecated
|
||||
SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth);
|
||||
|
||||
SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetTotalArticles());
|
||||
SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetSuccessArticles());
|
||||
SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetFailedArticles());
|
||||
|
||||
for (ServerStatList::iterator it = m_pPostInfo->GetNZBInfo()->GetServerStats()->begin(); it != m_pPostInfo->GetNZBInfo()->GetServerStats()->end(); it++)
|
||||
{
|
||||
ServerStat* pServerStat = *it;
|
||||
|
||||
char szName[50];
|
||||
|
||||
snprintf(szName, 50, "NZBPP_SERVER%i_SUCCESSARTICLES", pServerStat->GetServerID());
|
||||
szName[50-1] = '\0';
|
||||
SetIntEnvVar(szName, pServerStat->GetSuccessArticles());
|
||||
|
||||
snprintf(szName, 50, "NZBPP_SERVER%i_FAILEDARTICLES", pServerStat->GetServerID());
|
||||
szName[50-1] = '\0';
|
||||
SetIntEnvVar(szName, pServerStat->GetFailedArticles());
|
||||
}
|
||||
|
||||
PrepareEnvScript(m_pPostInfo->GetNZBInfo()->GetParameters(), szScriptName);
|
||||
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
|
||||
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int iExitCode)
|
||||
{
|
||||
// The ScriptStatus is accumulated for all scripts:
|
||||
// If any script has failed the status is "failure", etc.
|
||||
|
||||
switch (iExitCode)
|
||||
{
|
||||
case POSTPROCESS_SUCCESS:
|
||||
PrintMessage(Message::mkInfo, "%s successful", GetInfoName());
|
||||
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", GetInfoName());
|
||||
return ScriptStatus::srFailure;
|
||||
|
||||
case POSTPROCESS_NONE:
|
||||
PrintMessage(Message::mkInfo, "%s skipped", GetInfoName());
|
||||
return ScriptStatus::srNone;
|
||||
|
||||
#ifndef DISABLE_PARCHECK
|
||||
case POSTPROCESS_PARCHECK:
|
||||
if (m_pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped)
|
||||
{
|
||||
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName());
|
||||
return ScriptStatus::srFailure;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName());
|
||||
m_pPostInfo->SetRequestParCheck(true);
|
||||
m_pPostInfo->SetForceRepair(true);
|
||||
return ScriptStatus::srSuccess;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName());
|
||||
return ScriptStatus::srFailure;
|
||||
}
|
||||
}
|
||||
|
||||
void PostScriptController::AddMessage(Message::EKind eKind, const char* szText)
|
||||
{
|
||||
const char* szMsgText = szText + m_iPrefixLen;
|
||||
|
||||
if (!strncmp(szMsgText, "[NZB] ", 6))
|
||||
{
|
||||
debug("Command %s detected", szMsgText + 6);
|
||||
if (!strncmp(szMsgText + 6, "FINALDIR=", 9))
|
||||
{
|
||||
DownloadQueue::Lock();
|
||||
m_pPostInfo->GetNZBInfo()->SetFinalDir(szMsgText + 6 + 9);
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
else if (!strncmp(szMsgText + 6, "DIRECTORY=", 10))
|
||||
{
|
||||
DownloadQueue::Lock();
|
||||
m_pPostInfo->GetNZBInfo()->SetDestDir(szMsgText + 6 + 10);
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
else if (!strncmp(szMsgText + 6, "NZBPR_", 6))
|
||||
{
|
||||
char* szParam = strdup(szMsgText + 6 + 6);
|
||||
char* szValue = strchr(szParam, '=');
|
||||
if (szValue)
|
||||
{
|
||||
*szValue = '\0';
|
||||
DownloadQueue::Lock();
|
||||
m_pPostInfo->GetNZBInfo()->GetParameters()->SetParameter(szParam, szValue + 1);
|
||||
DownloadQueue::Unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
|
||||
}
|
||||
free(szParam);
|
||||
}
|
||||
else if (!strncmp(szMsgText + 6, "MARK=BAD", 8))
|
||||
{
|
||||
SetLogPrefix(NULL);
|
||||
PrintMessage(Message::mkWarning, "Marking %s as bad", m_pPostInfo->GetNZBInfo()->GetName());
|
||||
SetLogPrefix(m_pScript->GetDisplayName());
|
||||
m_pPostInfo->GetNZBInfo()->SetMarkStatus(NZBInfo::ksBad);
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
|
||||
}
|
||||
}
|
||||
else if (!strncmp(szMsgText, "[HISTORY] ", 10))
|
||||
{
|
||||
m_pPostInfo->GetNZBInfo()->AppendMessage(eKind, 0, szMsgText);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptController::AddMessage(eKind, szText);
|
||||
m_pPostInfo->AppendMessage(eKind, szText);
|
||||
}
|
||||
|
||||
if (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority())
|
||||
{
|
||||
time_t tStageTime = m_pPostInfo->GetStageTime();
|
||||
time_t tStartTime = m_pPostInfo->GetStartTime();
|
||||
time_t tWaitTime = time(NULL);
|
||||
|
||||
// wait until Post-processor is unpaused
|
||||
while (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority() && !IsStopped())
|
||||
{
|
||||
usleep(100 * 1000);
|
||||
|
||||
// update time stamps
|
||||
|
||||
time_t tDelta = time(NULL) - tWaitTime;
|
||||
|
||||
if (tStageTime > 0)
|
||||
{
|
||||
m_pPostInfo->SetStageTime(tStageTime + tDelta);
|
||||
}
|
||||
|
||||
if (tStartTime > 0)
|
||||
{
|
||||
m_pPostInfo->SetStartTime(tStartTime + tDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PostScriptController::Stop()
|
||||
{
|
||||
debug("Stopping post-process-script");
|
||||
Thread::Stop();
|
||||
Terminate();
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* This file is part of nzbget
|
||||
*
|
||||
* Copyright (C) 2007-2014 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 POSTSCRIPT_H
|
||||
#define POSTSCRIPT_H
|
||||
|
||||
#include "Thread.h"
|
||||
#include "Log.h"
|
||||
#include "QueueScript.h"
|
||||
#include "DownloadInfo.h"
|
||||
#include "Options.h"
|
||||
|
||||
class PostScriptController : public Thread, public NZBScriptController
|
||||
{
|
||||
private:
|
||||
PostInfo* m_pPostInfo;
|
||||
int m_iPrefixLen;
|
||||
Options::Script* m_pScript;
|
||||
|
||||
void PrepareParams(const char* szScriptName);
|
||||
ScriptStatus::EStatus AnalyseExitCode(int iExitCode);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(Options::Script* pScript);
|
||||
virtual void AddMessage(Message::EKind eKind, const char* szText);
|
||||
|
||||
public:
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
static void StartJob(PostInfo* pPostInfo);
|
||||
};
|
||||
|
||||
#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-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,20 +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 PREPOSTPROCESSOR_H
|
||||
#define PREPOSTPROCESSOR_H
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "Thread.h"
|
||||
#include "Observer.h"
|
||||
#include "DownloadInfo.h"
|
||||
@@ -35,49 +28,48 @@
|
||||
|
||||
class PrePostProcessor : public Thread
|
||||
{
|
||||
public:
|
||||
PrePostProcessor();
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
bool HasMoreJobs() { return m_jobCount > 0; }
|
||||
int GetJobCount() { return m_jobCount; }
|
||||
bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action,
|
||||
int offset, const char* text);
|
||||
void NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
|
||||
void NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
|
||||
|
||||
private:
|
||||
class DownloadQueueObserver: public Observer
|
||||
{
|
||||
public:
|
||||
PrePostProcessor* m_pOwner;
|
||||
virtual void Update(Subject* Caller, void* Aspect) { m_pOwner->DownloadQueueUpdate(Caller, Aspect); }
|
||||
PrePostProcessor* m_owner;
|
||||
virtual void Update(Subject* Caller, void* Aspect) { m_owner->DownloadQueueUpdate(Caller, Aspect); }
|
||||
};
|
||||
|
||||
private:
|
||||
ParCoordinator m_ParCoordinator;
|
||||
DownloadQueueObserver m_DownloadQueueObserver;
|
||||
int m_iJobCount;
|
||||
NZBInfo* m_pCurJob;
|
||||
const char* m_szPauseReason;
|
||||
ParCoordinator m_parCoordinator;
|
||||
DownloadQueueObserver m_downloadQueueObserver;
|
||||
int m_jobCount = 0;
|
||||
NzbInfo* m_curJob = nullptr;
|
||||
const char* m_pauseReason = nullptr;
|
||||
|
||||
bool IsNZBFileCompleted(NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bAllowOnlyOneDeleted);
|
||||
bool IsNZBFileDownloading(NZBInfo* pNZBInfo);
|
||||
void CheckPostQueue();
|
||||
void JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo);
|
||||
void StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo);
|
||||
void SaveQueue(DownloadQueue* pDownloadQueue);
|
||||
void SanitisePostQueue(DownloadQueue* pDownloadQueue);
|
||||
void CheckDiskSpace();
|
||||
void UpdatePauseState(bool bNeedPause, const char* szReason);
|
||||
void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
|
||||
void NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
|
||||
void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue);
|
||||
bool PostQueueDelete(DownloadQueue* pDownloadQueue, IDList* pIDList);
|
||||
void DeletePostThread(PostInfo* pPostInfo);
|
||||
NZBInfo* GetNextJob(DownloadQueue* pDownloadQueue);
|
||||
void DownloadQueueUpdate(Subject* Caller, void* Aspect);
|
||||
void DeleteCleanup(NZBInfo* pNZBInfo);
|
||||
|
||||
public:
|
||||
PrePostProcessor();
|
||||
virtual ~PrePostProcessor();
|
||||
virtual void Run();
|
||||
virtual void Stop();
|
||||
bool HasMoreJobs() { return m_iJobCount > 0; }
|
||||
int GetJobCount() { return m_iJobCount; }
|
||||
bool EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText);
|
||||
void NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
|
||||
void NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
|
||||
bool IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars);
|
||||
bool IsNzbFileDownloading(NzbInfo* nzbInfo);
|
||||
void CheckPostQueue();
|
||||
void JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo);
|
||||
void StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo);
|
||||
void SanitisePostQueue();
|
||||
void UpdatePauseState(bool needPause, const char* reason);
|
||||
void NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
|
||||
void NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
|
||||
void NzbCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, bool saveQueue);
|
||||
bool PostQueueDelete(DownloadQueue* downloadQueue, IdList* idList);
|
||||
void DeletePostThread(PostInfo* postInfo);
|
||||
NzbInfo* GetNextJob(DownloadQueue* downloadQueue);
|
||||
void DownloadQueueUpdate(Subject* Caller, void* Aspect);
|
||||
void DeleteCleanup(NzbInfo* nzbInfo);
|
||||
};
|
||||
|
||||
extern PrePostProcessor* g_PrePostProcessor;
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user