Compare commits

...

247 Commits
v17.1 ... v19.0

Author SHA1 Message Date
Andrey Prygunkov
810ddc8356 updated version string to "19.0" 2017-06-25 19:21:32 +02:00
Andrey Prygunkov
1a840f894e updated ChangeLog for v19.0 2017-06-25 19:05:04 +02:00
Andrey Prygunkov
928e0a6006 fixed #399: error when compiling without par-check 2017-06-23 23:22:49 +02:00
Andrey Prygunkov
a4cca673dc fixed #398: crash between post-processing steps 2017-06-16 17:51:28 +02:00
Andrey Prygunkov
fb9b84a23b #371: wait for direct rename completion before direct unpack
Redo accidentally undone 5df06a2626.
2017-06-16 16:45:55 +02:00
Andrey Prygunkov
bc2b9de6a9 #392: added link to wiki page 2017-06-10 18:07:31 +02:00
Simon Nicolussi
7793f64b77 #386: don't write beyond buffer when reading a signature 2017-06-10 18:04:30 +02:00
Andrey Prygunkov
cad3116b5b #392: Windows crash dump support
Also renamed option "DumpCore" to "CrashDump"; new option "CrashTrace".
2017-06-10 13:13:16 +02:00
Andrey Prygunkov
dd714355c4 nzbget/nzbget#388: updated wiki links to use new url format 2017-06-09 18:41:40 +02:00
Andrey Prygunkov
0a73a0c31f fixed #387: asterix passed as parameter to extension scripts
(Windows only)
2017-06-07 18:51:16 +02:00
Andrey Prygunkov
7f393d050c #362: removed unnecessary code 2017-06-07 17:25:24 +02:00
Andrey Prygunkov
5dcca7294f #371: respect PostStrategy when starting another direct unpack
to avoid running way too many direct unpack jobs at the same time.
2017-06-06 15:32:24 +02:00
Andrey Prygunkov
53da5725c2 #362, #382: articles yEncoded with less than 68 characters
are now correctly processed by direct rename.
2017-05-31 22:00:25 +02:00
Andrey Prygunkov
77cabd7bce #371: do not direct unpack if article decoding is disabled
via option Decode=no.
2017-05-30 21:19:38 +02:00
Andrey Prygunkov
2336d4bcfe #362: do not direct rename if article decoding is disabled
via option Decode=no.
2017-05-30 21:18:54 +02:00
Andrey Prygunkov
580e1974bc #362, #382: fixed crash during direct rename
which may happen if articles were yEncoded with less than 68 character
length.
2017-05-30 21:17:47 +02:00
Andrey Prygunkov
8790ee685f updated link to documentation 2017-05-26 00:36:51 +02:00
Andrey Prygunkov
32f0bbae58 #371: do not terminate unrar if it's not running
when cancelling direct unpack
2017-05-23 21:05:25 +02:00
Andrey Prygunkov
8ffd6c24fe #364: do not reorder files on nzb parse errors 2017-05-23 20:53:11 +02:00
Andrey Prygunkov
98cc4817fe #362, #382: fixed crash during direct rename
which may happen if download errors occurred.
2017-05-23 20:51:23 +02:00
Andrey Prygunkov
14b40d6712 #362: discard unneeded data after direct rename
Now also discarding data when download completes without direct rename
being able to process files (due to download errors).
2017-05-23 19:39:09 +02:00
Andrey Prygunkov
629640898d #362: prevent repeating of error messages
If a file got lost before cache flushing.
2017-05-23 19:21:07 +02:00
Andrey Prygunkov
5df06a2626 #371: wait for direct rename completion before direct unpack 2017-05-22 22:08:09 +02:00
Andrey Prygunkov
4ca95b2989 #371: reset direct unpack status on post-process again 2017-05-22 22:05:52 +02:00
Andrey Prygunkov
b3cc316092 #252: new option to force news servers to ipv4 or ipv6 2017-05-22 22:03:30 +02:00
Andrey Prygunkov
298fde6cd4 #379: compile unrar for Linux with fallocate 2017-05-20 14:05:24 +02:00
Andrey Prygunkov
015b3be461 #371: better cleanup on cancelling unpack
Deleting destination if it contains only hidden files.
2017-05-18 22:00:45 +02:00
Andrey Prygunkov
f6adbe848d #362: avoid unnecessary renaming of par-sets 2017-05-18 20:32:46 +02:00
Andrey Prygunkov
08de827d7b refactor: removed unneeded type casts 2017-05-18 18:43:08 +02:00
Andrey Prygunkov
d1bda91954 refactor: removed one override for FileExists 2017-05-17 21:26:27 +02:00
Andrey Prygunkov
e40e3178da #378: removed debug logging 2017-05-16 19:38:01 +02:00
Andrey Prygunkov
cf3985f228 #378: save nzb download statistics on idle or reload 2017-05-16 19:19:54 +02:00
Andrey Prygunkov
f2329dada4 #362: log statistics for direct rename 2017-05-16 18:57:32 +02:00
Andrey Prygunkov
a73d1ba56e #31: backup icon in history in phone theme 2017-05-12 19:14:02 +02:00
Andrey Prygunkov
8d5ce3ddc3 fixed #376: startup scheduler tasks can be executed again 2017-05-12 19:08:31 +02:00
Andrey Prygunkov
d3362f9280 #371: detect not unpacked archives
Detect not unpacked archives which may appear after rar-rename and
unpack them during post-processing.
2017-05-12 18:37:15 +02:00
Andrey Prygunkov
85dcde36c5 374: unrar tests for Travis CI 2017-05-09 21:46:15 +02:00
Andrey Prygunkov
716bb3130f #374: added gcc 4.8 to Travis CI test matrix 2017-05-09 21:45:32 +02:00
Andrey Prygunkov
725e2c7376 #371: fixed zombies after reload during unpack 2017-05-09 20:36:48 +02:00
Andrey Prygunkov
3a07dd378a #371: restart direct unpack after program reload
When program is reloaded during direct unpack the unpack restarts after
one (any) inner file is downloaded.
2017-05-08 23:27:49 +02:00
Andrey Prygunkov
ff02b53ed0 #371: better handling of cancelling direct unpack 2017-05-06 23:45:31 +02:00
Andrey Prygunkov
39089b6f2f #371: disable direct unpack tests in Travis CI
because it misses unrar 5 required for the tests
2017-05-05 22:49:03 +02:00
Andrey Prygunkov
61af2b3446 #371: integrated direct unpack into pp-workflow 2017-05-05 21:57:52 +02:00
Andrey Prygunkov
20f4f3020b #371: 6b546394b2: corrected stdin redirection 2017-05-05 21:56:54 +02:00
Andrey Prygunkov
df5a1fe959 #371: direct unpack volume handling
Archive parts are now unpacked as they arrive.
2017-05-03 21:42:59 +02:00
Andrey Prygunkov
6b546394b2 #371: added stdin-redirecting support 2017-05-03 21:39:57 +02:00
Andrey Prygunkov
160b274ce8 #371: new module "DirectUnpack"
without implementation and with a new failing unit test.
2017-05-03 21:30:35 +02:00
Andrey Prygunkov
4104a2357b #371: new option "DirectUnpack"
without implementation.
2017-05-03 21:08:21 +02:00
Andrey Prygunkov
ba1e51a8d8 #362: new queue event after direct rename
Queue-event NZB_NAMED, sent after the inner files are renamed
2017-05-01 17:48:20 +02:00
Andrey Prygunkov
9f7d6655b2 #364: logging for file reordering 2017-05-01 17:31:14 +02:00
Andrey Prygunkov
f6a9253a53 #364: fixed compiling error on Linux 2017-04-30 21:41:23 +02:00
Andrey Prygunkov
48020e8901 #361: fixed failing test 2017-04-30 21:36:35 +02:00
Andrey Prygunkov
5afa20d655 #364: better ordering for rar- and par-files 2017-04-30 21:27:51 +02:00
Andrey Prygunkov
197baf066a updated option descriptions in configuration file 2017-04-30 14:39:46 +02:00
Andrey Prygunkov
c873647aae #361: new option "FileNaming"
replace pp-parameter “*naming”.
2017-04-30 14:21:49 +02:00
Andrey Prygunkov
fc1847588d #362: save and restore disk state for direct rename 2017-04-30 00:10:17 +02:00
Andrey Prygunkov
62b3d47b43 #362: redesigned renaming
Spread RenameInfo into FileInfo and NzbInfo to make the state easier to
save on disk
2017-04-28 23:55:41 +02:00
Andrey Prygunkov
ddb9333ca6 #362: discard partially downloaded par2-files
to solve issues with par-checker, par-renamer and avoid saving state
for these files.
2017-04-28 18:36:27 +02:00
Andrey Prygunkov
830e0e4858 #362: proper rename on completion
Fixed: if the file was renamed during finalizing stage (completion) the
file may not be properly renamed.
2017-04-27 17:31:39 +02:00
Andrey Prygunkov
085c612f97 #24: fixed errors when adding many files at once 2017-04-25 21:36:53 +02:00
Andrey Prygunkov
dea9fb2090 #24: double click handler for windows 2017-04-25 19:33:31 +02:00
Andrey Prygunkov
5813c903eb #362: compatibility with gcc 4.8 2017-04-25 19:27:39 +02:00
Andrey Prygunkov
4f499e2c2e fixed #367: fatal-messages when compiling from sources 2017-04-25 19:26:53 +02:00
wtf911
3884328251 #365, #24: option for file association in windows installer
This will add a checkbox so after installation finishes there will be an option to associate .nzb files with NZBGet. It will also remove the association if NZBGet is uninstalled.
2017-04-25 00:40:19 +02:00
Andrey Prygunkov
7e9e2471ef #362: handling of download interruptions
if they happen during downloading of first articles
2017-04-24 23:10:14 +02:00
Andrey Prygunkov
2a433ee7fb #362: fixed inner sorting 2017-04-24 23:09:05 +02:00
Andrey Prygunkov
118f835385 #362: multiple par-sets and renaming par2-files 2017-04-23 20:20:59 +02:00
Andrey Prygunkov
9a6a42bd44 #362: unpause par2-file
as we need it to download first and ExtraPriority-flag doesn’t work on
paused files
2017-04-23 13:48:39 +02:00
Andrey Prygunkov
9434149842 #364: remote command for sorting of inner files
subcommand “SF” of remote command “-E“
2017-04-22 21:12:47 +02:00
Andrey Prygunkov
4107536c03 #364: implemented file reordering
- reordering inner files after adding nzb to queue;
- reordering inner files after adding direct renaming;
- new command “GroupSortFiles” in api-method “editqueue”.
2017-04-22 20:13:45 +02:00
Andrey Prygunkov
2aec782f58 #364: new option "ReorderFiles" 2017-04-22 20:10:00 +02:00
Andrey Prygunkov
eaaa943af3 #362: save new filenames into disk state 2017-04-21 20:33:23 +02:00
Andrey Prygunkov
964e8311a9 #362: renaming files 2017-04-20 21:28:06 +02:00
Andrey Prygunkov
895dd12e4d #362: another functional test for direct renamer 2017-04-19 18:59:09 +02:00
Andrey Prygunkov
d16036aa78 #362: loading hashes from par2-flle 2017-04-19 16:43:58 +02:00
Andrey Prygunkov
8b79a81eaf #362: functional tests for direct renamer
(new tests currently fail)
2017-04-18 19:58:12 +02:00
Andrey Prygunkov
231e94dd2e #362: detecting par2-files 2017-04-18 19:57:35 +02:00
Andrey Prygunkov
f7be22893d #362: computing 16k-hashes for downloaded files 2017-04-17 15:32:32 +02:00
Andrey Prygunkov
3ac91a4bb6 #362: fixed restoring of partial state
when direct write was disabled
2017-04-16 15:01:34 +02:00
Andrey Prygunkov
43441a8d55 #362: downloading first article of each file
, then other articles, when option “DirectRename” is active.
2017-04-16 14:58:54 +02:00
Andrey Prygunkov
b02059f196 #362: new option "DirectRename" 2017-04-16 14:57:10 +02:00
Andrey Prygunkov
f107802f0e #361: pp-param "*naming"
to define naming scheme for downloaded files: “nzb” - use file names
from nzb, “article” - use file names from article metadata (default).
2017-04-15 01:39:40 +02:00
Andrey Prygunkov
7faf1fe64b #361: prefer file names from nzb
to names from article body; to better handle obfuscated posts.
2017-04-14 20:36:00 +02:00
Andrey Prygunkov
bf2fea64e7 #360: removed unnecessary requests to news servers 2017-04-14 14:39:37 +02:00
Andrey Prygunkov
de3eb3de9d #352: parameters for api-method "servervolumes"
as a performance optimization measure to reduce amount of transferred
data.
2017-04-12 19:47:58 +02:00
Andrey Prygunkov
55d080ac96 #50: clear script execution log
before executing script
2017-04-12 19:41:28 +02:00
Andrey Prygunkov
795bacb4fe merge from master v18.1 2017-04-09 13:38:01 +02:00
Andrey Prygunkov
f7930b56a6 updated version string to "18.1" 2017-04-08 17:37:57 +02:00
Andrey Prygunkov
b2bf488d59 fixed #338: "undefined" in reorder extension scripts
When editing option “ScriptOrder” in web-interface.
2017-04-08 17:28:41 +02:00
Andrey Prygunkov
8e4b75b21e fixed #347: possible crash at the end of post-processing 2017-04-08 17:27:51 +02:00
Andrey Prygunkov
5b17bebbd6 fixed #348: queue was not saved after deleting of queued post-jobs 2017-04-08 17:27:22 +02:00
Andrey Prygunkov
bda1eaa192 fixed #350: sleep mode no longer works on Windows 2017-04-08 17:26:15 +02:00
Andrey Prygunkov
4bcdbbeb09 fixed #356: crash during download caused by a race condition 2017-04-08 17:25:29 +02:00
Andrey Prygunkov
f0ee12f92e #352: option "ServerX.Notes"
for user comments on news servers.
2017-04-07 20:43:06 +02:00
Andrey Prygunkov
0b14a2f869 #352: showing volume stats from settings page
New button “Volume Statistics” in section “News Servers” of settings
page. Shows the same volume data as in global statistics dialog.
2017-04-07 20:40:46 +02:00
Andrey Prygunkov
2579ce65b9 fixed #356: crash during download caused by a race condition 2017-03-31 17:46:02 +02:00
Andrey Prygunkov
77f86988cb #353: support for ECC certificates in built-in web-server 2017-03-21 23:29:48 +01:00
Andrey Prygunkov
b9b62dcd75 #353: better error reporting for TLS error in built-in web-server 2017-03-21 23:29:07 +01:00
Andrey Prygunkov
bfee7c55cd #50: confirmation for commands marked as dangerous 2017-03-21 23:22:54 +01:00
Andrey Prygunkov
34efa87699 #50: "send test e-mail" command in EMail.py 2017-03-20 21:03:40 +01:00
Andrey Prygunkov
e839db7107 #50: pass not yet saved options to script 2017-03-20 20:59:34 +01:00
Andrey Prygunkov
ffb16aa7bb #50: progress dialog for script execution log
New API-method “logscript”.
2017-03-20 20:30:19 +01:00
Andrey Prygunkov
a3b0b7675e #50: execute custom commands for scripts
New API-method “startscript”.
2017-03-17 20:24:14 +01:00
Andrey Prygunkov
602f62c17c #50: define custom buttons in extension scripts 2017-03-17 19:23:56 +01:00
Andrey Prygunkov
ace7a1968d #349: added host name to all error messages 2017-03-15 20:36:45 +01:00
Andrey Prygunkov
6efefe3780 fixed #350: sleep mode no longer works on Windows 2017-03-15 20:13:55 +01:00
Andrey Prygunkov
c02f708d74 #346: save changes before performing actions in history dialog 2017-03-13 19:53:54 +01:00
Andrey Prygunkov
e7c47f523a fixed #348: queue was not saved after deleting of queued post-jobs 2017-03-13 18:04:44 +01:00
Andrey Prygunkov
5c8be152f4 fixed #347: possible crash at the end of post-processing 2017-03-13 17:58:15 +01:00
Andrey Prygunkov
4cdbfc84dd #346: changing category via drop-down changes pp-parameters
accordingly to the new category.
2017-03-12 20:09:50 +01:00
Andrey Prygunkov
75ec856af3 #346: improved hover effect
Increased clickable area and made the carets (triangles) clickable too.
2017-03-11 23:21:50 +01:00
Andrey Prygunkov
88b5e16597 #346: multi-edit via drop-downs
If multiple items are selected and drop-down is shown for one of them
the menus work on all selected items.
2017-03-09 20:55:04 +01:00
Andrey Prygunkov
fff6fdd4eb #346: unified hover feedback for all drop-downs 2017-03-09 20:44:57 +01:00
Andrey Prygunkov
c4ff544459 #346: drop-down for status in downloads and history 2017-03-08 20:16:17 +01:00
Andrey Prygunkov
9c87942b43 #346: drop-down for category in history 2017-03-08 20:12:38 +01:00
Andrey Prygunkov
0e4c9275b0 #346: drop-down for category in downloads 2017-03-08 20:10:23 +01:00
Andrey Prygunkov
f11dce4269 #346: drop-down for priority in downloads 2017-03-08 20:07:25 +01:00
Andrey Prygunkov
f1340eb542 #263: improvements in RSS feed view in phone mode 2017-03-04 18:17:14 +01:00
Andrey Prygunkov
e89f5cbd8a #31: backup-badge for items in history
if backup news servers were used; that’s similar to backup-badge on
downloads tab.
2017-02-26 21:35:09 +01:00
Andrey Prygunkov
975016700e #23: proper exit code on client command success or failure 2017-02-26 20:40:55 +01:00
Andrey Prygunkov
a9017be606 #123: set name, password and dupe info when adding via URL
by click on a button near URL field in web-interface
2017-02-26 19:23:33 +01:00
Andrey Prygunkov
d81d6831dc #331: support for HTTP-header "X-Forwarded-For" in IP-logging 2017-02-26 12:40:37 +01:00
Andrey Prygunkov
1b94373c7e fixed #338: "undefined" in reorder extension scripts
When editing option “ScriptOrder” in web-interface.
2017-02-24 16:27:05 +01:00
Andrey Prygunkov
d5e881ce1a #330: optimisations for Safari 2017-02-24 14:55:05 +01:00
Andrey Prygunkov
a3f84aca0e #330: better session handling in form login 2017-02-23 20:49:50 +01:00
Andrey Prygunkov
0ab86b90f0 #330: authentication via form in web-interface
, new option “FormAuth”.
2017-02-22 17:41:25 +01:00
Andrey Prygunkov
b6a606db35 #339: extended error messages with a link to wiki-page 2017-02-20 18:24:30 +01:00
Andrey Prygunkov
578731f239 #339: integrated root certificate store file into Linux installer 2017-02-19 18:14:04 +01:00
Andrey Prygunkov
71db4ffe9c #192: adjusted linux build script 2017-02-19 18:12:23 +01:00
Andrey Prygunkov
7421ec7b1a #339: added missing "define" for Windows build 2017-02-19 15:29:14 +01:00
Andrey Prygunkov
958712663e fixed compile error under gcc 4.8 2017-02-18 20:14:48 +01:00
Andrey Prygunkov
d96fa66487 #339: 36ac548842: fixed compile error on gcc and clang 2017-02-18 20:12:53 +01:00
Andrey Prygunkov
36ac548842 #339: TLS certificate verification with GnuTLS 2017-02-18 19:47:20 +01:00
Andrey Prygunkov
fc44ab6128 #339: simplified verification code 2017-02-17 23:44:15 +01:00
Andrey Prygunkov
f0da3936e5 #339: prevent compilation failure on older OpenSSL versions 2017-02-17 13:22:07 +01:00
Andrey Prygunkov
04e694799d #339: TLS certificate verification with OpenSSL 2017-02-17 12:32:20 +01:00
Andrey Prygunkov
712cedb84f #339: new options "CertStore" and "CertCheck" 2017-02-17 11:27:49 +01:00
Andrey Prygunkov
4fb3ddcf84 updated version string to "19.0-testing" 2017-02-17 11:03:57 +01:00
Andrey Prygunkov
69d40c11fd Merge branch 'develop' for v18.0 2017-02-12 17:20:07 +01:00
Andrey Prygunkov
58893710d8 updated version string to "18.0" 2017-02-12 17:05:21 +01:00
Andrey Prygunkov
6c6f781510 updated ChangeLog for v18.0 2017-02-12 17:03:04 +01:00
Andrey Prygunkov
569ec22ee8 updated copyright string in Windows and OS X apps 2017-02-12 17:02:56 +01:00
Andrey Prygunkov
b06d3eca86 #327: printing only base file name in warnings 2017-02-12 14:35:44 +01:00
Andrey Prygunkov
dcf63b4db7 #277: allow control of what tab is shown when opening web-interface
Add “#downloads”, “#history”, “#messages” or “#settings” to the URL,
for example “http://localhost:6789/#history” or
“http://localhost:6789/index.html#history”
2017-01-27 20:10:57 +01:00
Andrey Prygunkov
06e7573572 #329: improved selecting of par2-file for repair 2017-01-19 21:48:35 +01:00
Andrey Prygunkov
639e0b6bfb #327: reverted non-strict par2-filename matching
To handle article subjects with non-parseable filenames. Also created a
functional test for this case.
2017-01-17 22:18:34 +01:00
Andrey Prygunkov
f2073ff920 #327: better handing of damaged par2-files in par-renamer
If par-renamer can’t load par2-file another par2-file is downloaded and
par-renamer tries again.
2017-01-17 00:23:29 +01:00
Sander
4ba2e07d94 #328: NServ: option -b to specify on which address to listen 2017-01-08 19:37:43 +01:00
Sander
91515b20e5 #324, #325: Nserv: Check if specified data-dir is an existing directory 2017-01-06 21:11:42 +01:00
Andrey Prygunkov
7226e1c186 #319: fixed automatic changing of pp-scripts when changing category in web-interface 2016-12-30 22:29:14 +01:00
Andrey Prygunkov
98c2dd46b7 #291: fixed "PostTotalTimeSec" in API-method "listgroups" 2016-12-27 17:21:06 +01:00
Andrey Prygunkov
f0b08dbd63 #315: reset feed search box 2016-12-22 20:24:00 +01:00
Sander
25ddfd5659 #320, #321: newlines added to 281 and 205 server answers 2016-12-21 19:46:55 +01:00
Andrey Prygunkov
7450b97871 #319: scheduler scripts in option "Extensions"
Scheduler scripts can now be selected in option “Extensions” if the
scripts provide default time definition.
2016-12-20 22:22:09 +01:00
Andrey Prygunkov
9f7e0ee972 #304: safer termination of scripts 2016-12-19 19:33:38 +01:00
Andrey Prygunkov
ae7719e948 #319: auto migration of old script settings 2016-12-16 21:03:59 +01:00
Andrey Prygunkov
9a92896678 #319: unified extension scripts settings
- new option “Extension” as replacement for options “PostScript”,
“QueueScript”, “ScanScript”, “FeedScript”;
- renamed “CategoryX.PostScript” to “CategoryX.Extensions”;
- renamed “FeedX.PostScript” to “FeedX.Extensions”.
2016-12-14 20:34:14 +01:00
Andrey Prygunkov
ab0723adda #288: rar-rename only if unpack is enabled 2016-12-14 18:50:14 +01:00
Andrey Prygunkov
13b98fca83 fixed incompatibility with macOS El Capitan and XCode 7/8 2016-12-13 21:52:32 +01:00
Andrey Prygunkov
5901998cb5 #242: fixed enter-key in feed filter dialog 2016-12-10 20:13:19 +01:00
Andrey Prygunkov
9ddd73368e #282: 0d67e322a3: fixed confirmation dialog 2016-12-07 21:02:55 +01:00
Andrey Prygunkov
12daa683e5 #313: 5f71c4a5a7: fixed test failure on Linux 2016-12-06 19:18:06 +01:00
Andrey Prygunkov
5f71c4a5a7 #313: better handling of renamed par-files 2016-12-06 18:49:12 +01:00
Andrey Prygunkov
13db817395 #313: better handling of obfuscated par-files in par-renamer
Avoid false detection of missing files.
2016-12-04 19:04:39 +01:00
Andrey Prygunkov
84f4cf2f33 #312: extended description of option "KeepHistory" 2016-12-03 12:54:49 +01:00
Andrey Prygunkov
0d67e322a3 #282: extra warning when deleting from history
An extra warning is shown when deleting 50 or more records from history
and there are selected records on other pages.
2016-11-29 18:56:00 +01:00
Andrey Prygunkov
374f706354 #282: visual indication of records selected on other pages 2016-11-22 21:36:20 +01:00
Andrey Prygunkov
f7cefadf33 use default SDK when building mac app 2016-11-20 19:31:06 +01:00
Andrey Prygunkov
c111a114b9 #242: keyboard support in web-interface 2016-11-20 14:24:27 +01:00
Andrey Prygunkov
2cd3f0fc68 #309: allow sending e-mails to multiple recipients
in the example pp-script “EMail.py”.
2016-11-20 13:12:50 +01:00
Andrey Prygunkov
92db59116e #301: showing drag grip
and extended the drag grip area to the full cell containing check box.
2016-11-18 19:59:33 +01:00
Andrey Prygunkov
760aed68fb #288: fixed incorrect renaming
of archives containing directory entries
2016-11-16 21:41:34 +01:00
Andrey Prygunkov
e612257c28 #304: graceful termination of scheduler scripts
If a script is running when the program must shutdown, the script
receives signal SIGINT (CTRL+BREAK on Windows) and has 10 seconds to
gracefully terminate until it is killed.
2016-11-15 19:22:52 +01:00
Andrey Prygunkov
31208a5816 #304: scheduler tasks can be started at program launch
Using asterisk as TaskX.Time.
2016-11-15 19:00:49 +01:00
Andrey Prygunkov
a03e7cd550 #295: disabled SSLv3 in built-in web-server 2016-11-15 18:47:30 +01:00
Andrey Prygunkov
02b33fb559 #306: splitted options "Retries" and "RetryInterval"
into four options: ArticleRetries, ArticleInterval, UrlRetries, and
UrlInterval.
2016-11-13 13:05:37 +01:00
Andrey Prygunkov
a9ed53daa8 fixed #303: rar tests fail with TLS disabled 2016-11-12 18:37:48 +01:00
Andrey Prygunkov
a3c460ed40 #301: showing row content while dragging
Implemented alternative UI for drag and drop.
2016-11-12 14:23:19 +01:00
Andrey Prygunkov
df11d1acb4 #301: functional tests for api-method "editqueue"
And a small fix in edit-commands GroupMoveAfter and GroupMoveBefore.
2016-11-08 21:02:36 +01:00
Andrey Prygunkov
e49d4c59af #301: auto scrolling during dragging 2016-11-06 21:16:19 +01:00
Andrey Prygunkov
5dc9c07a58 #301: moved drag-n-drop code into module fasttable.js
Ready for reuse on other places.
2016-11-04 22:02:26 +01:00
Andrey Prygunkov
3bb0751c86 #301: drag-n-drop for touch screens
; also no more dragging via priority-icon but only via check mark.
2016-11-03 19:20:03 +01:00
Andrey Prygunkov
a3b3921bea #301: API extension for drag-n-drop support
New actions “GroupMoveBefore” and “GroupMoveAfter” in API-method
“editqueue”; parameter “args” contains id of the target item to move
before/after.
2016-11-01 12:27:42 +01:00
Andrey Prygunkov
b19d26aee8 #301: drag and drop handling in web-interface 2016-11-01 12:23:33 +01:00
Andrey Prygunkov
7fae337360 fixed #300: sorting of selected items may give wrong results 2016-10-31 19:05:15 +01:00
Andrey Prygunkov
80debf521a #299: removed parameter "offset" from api-method "editqueue"
When needed the “offset” is now passed within parameter “Args” as
string.
2016-10-31 15:58:02 +01:00
Andrey Prygunkov
528133482e #298: added compatibility with openssl 1.1.0 2016-10-31 10:11:23 +01:00
Andrey Prygunkov
a98bbd7d0d #279: cache support in built-in nntp server 2016-10-29 16:55:12 +02:00
Andrey Prygunkov
2681fe187b #288: sooner detection of damaged rar-files 2016-10-22 15:05:19 +02:00
Andrey Prygunkov
e16cab67fb #288: ed4761db37: reverted std-input handling on Windows 2016-10-22 15:04:11 +02:00
Andrey Prygunkov
50e2e0cf37 #291: refactor: handling of shutdown/reload 2016-10-18 21:53:48 +02:00
Andrey Prygunkov
d2d20f29c1 #291: better handling of shutdown/reload
for post-processing jobs
2016-10-18 21:52:30 +02:00
Andrey Prygunkov
53f4992eeb #279: corrected generation of test files 2016-10-18 21:51:22 +02:00
Andrey Prygunkov
3ae1be1ca4 #294: handling of incomplete obfuscated rar-archives
and special file extensions; new option “UnpackIgnoreExt” honored by
rar-renamer and unpacker.
2016-10-16 13:21:58 +02:00
Andrey Prygunkov
ce1e1b61e7 #291: fixed status in api-method "listgroups" 2016-10-15 16:12:48 +02:00
Andrey Prygunkov
cac25ed290 #291: distinguishing pp-stages par-renaming and rar-renaming
to better handling temporary pause.
2016-10-15 13:37:34 +02:00
Andrey Prygunkov
8ed0b70df5 #291: eacfacc5a2: fixed compilation error on Windows 2016-10-15 13:11:54 +02:00
Andrey Prygunkov
d72071c8ed #291: temporary pause during post-processing
Options ParPauseQueue, UnpackPauseQueue, PostPauseQueue work properly.
2016-10-15 13:00:52 +02:00
Andrey Prygunkov
ba05dfc202 #291: tweaking balanced pp-startegy
Other job can be run only during repair stage but not during
verification stage.
2016-10-14 11:20:57 +02:00
Andrey Prygunkov
f3adab5690 #293: split section "Download Queue"
Many options moved into new section “Connection”.
2016-10-14 00:19:14 +02:00
Andrey Prygunkov
b1b5405809 #291: new option "PostStrategy" to configure multi-post-processing
instead of option “ParExclusive”.
2016-10-14 00:07:13 +02:00
Andrey Prygunkov
99fcd164ac #291: smoother transition between pp-stages
without intermediate “pp-queued” state shown in web-interface
2016-10-13 00:07:44 +02:00
Andrey Prygunkov
eacfacc5a2 #291: non-global cout/cerr in par2-module
Fixed global cout/cerr in par2-module to prevent crashes when executing
multiple par2-instances.
2016-10-12 16:00:08 +02:00
Andrey Prygunkov
3404b544de #291: removed not used unit of par2-module 2016-10-12 15:58:08 +02:00
Andrey Prygunkov
3604534850 #291: functional tests for multi-processing 2016-10-12 00:30:23 +02:00
Andrey Prygunkov
2e18021e08 #291: new option "ParExclusive"
to configure simultaneous post-processing of a second item if currently
performing par-check/repair for the first item in pp-queue
2016-10-12 00:22:34 +02:00
Andrey Prygunkov
2e19382ccc #291: multi post-processing
During par-check/repair another pp-item can be post-processed at the
same time, as long as it doesn’t require par-check/repair.
2016-10-12 00:13:50 +02:00
Andrey Prygunkov
a6dc20dc8e #291: refactor: made par-checker non-global
and more similar to other post-processing stages.
2016-10-11 20:43:11 +02:00
Andrey Prygunkov
a1bef9146e #286: corrected html in priority column 2016-10-08 15:33:04 +02:00
Andrey Prygunkov
61f18f81b7 #29, #278: small UI tweak 2016-10-08 15:32:26 +02:00
Andrey Prygunkov
129125faa1 #286: priority column and sorting
Also removed horizontal lines in tables, for better visuals.
2016-10-07 23:46:21 +02:00
Andrey Prygunkov
b97c987f66 #274: fixed error on history page
Fixed javascript error on history page when showing hidden items.
2016-10-04 22:11:32 +02:00
Andrey Prygunkov
53e504d974 fixed failing functional tests 2016-10-03 23:39:34 +02:00
Andrey Prygunkov
5885258c35 #288: reading encrypted archives using nettle library
when compiled with GnuTLS instead of OpenSSL.
2016-10-03 22:38:48 +02:00
Andrey Prygunkov
2034ea97d2 #288: fixed compilation error with GnuTLS
and added unit test for rar3 encrypted files
2016-10-03 15:05:29 +02:00
Andrey Prygunkov
2cc38a85df #288: fixed incompatibility with older compilers 2016-10-03 01:35:46 +02:00
Andrey Prygunkov
ccd509b70c #288: reading rar3 encrypted archives
Rar-renamer now supports renaming of rar3 archives with encrypted
headers (encrypted filenames). Only when compiled with OpenSSL as TLS
library.
2016-10-02 23:10:31 +02:00
Andrey Prygunkov
bab43d8d30 #288: reading rar5 encrypted archives
Rar-renamer now supports renaming of rar5 archives with encrypted
headers (encrypted filenames). Only when compiled with OpenSSL as TLS
library.
2016-09-30 21:39:35 +02:00
Andrey Prygunkov
ed4761db37 #288: unit and functional tests for encrypted rar files 2016-09-30 19:32:55 +02:00
Andrey Prygunkov
db7bc4314f #288: rar-rename as a separate post-processing stage
Made rar-rename a separate post-processing stage which runs after
par-repair, in order to restore archive file names after possible
repair stage (which could rename files back to obfuscated names if
rar-rename were run before par-repair).
2016-09-28 22:22:18 +02:00
Andrey Prygunkov
8a21626bf6 #288: sophisticated renaming for rar-files 2016-09-27 23:34:42 +02:00
Andrey Prygunkov
804f8ab085 #288: integrated rar-renamer into post-processing
also implemented initial simple renaming for rar-files
2016-09-26 21:55:41 +02:00
Andrey Prygunkov
3593990dd6 #288: new module "Rename" to handle par-rename and rar-rename 2016-09-26 21:16:27 +02:00
Andrey Prygunkov
3a38a2c7c6 #288: refactor: moved parts of module "RarRenamer" into new module "RarReader"
Also added new unit test for module “RarReader”.
2016-09-26 21:01:15 +02:00
Andrey Prygunkov
16aef2e7a8 #288: functional tests for par-renamer and rar-renamer
Rar-renamer tests are currently failing as the feature isn’t completed
yet.
2016-09-25 21:08:45 +02:00
Andrey Prygunkov
bf992195e8 #288: fixed unit test for rar-renamer 2016-09-25 21:07:23 +02:00
Andrey Prygunkov
2fb0fd1113 #288: reading rar5-files 2016-09-24 22:43:20 +02:00
Andrey Prygunkov
709c9856c9 #288: reading rar3-files 2016-09-24 22:39:45 +02:00
Andrey Prygunkov
9e9ef3120f #288: added module "RarRenamer" with unit tests
The tests are failing at the moment as the module is not fully
implemented yet.
2016-09-24 18:37:02 +02:00
guaguasi
a7525ae6f9 #274, #285: password-badge for nzbs with passwords 2016-09-23 21:28:12 +02:00
Andrey Prygunkov
5e0e91f671 #249: do not cleanup if unpack disabled
If parameter "unpack" is disabled for an nzb-file the cleanup isn't
performed for it.
2016-09-19 23:40:00 +02:00
Andrey Prygunkov
09f863f3af #275: nZEDb attributes in rss feeds
Added support for nZEDb attributes in rss feeds.
2016-09-19 23:37:51 +02:00
Andrey Prygunkov
87e8d479d8 #281: improved error reporting on feed parse errors 2016-09-19 22:22:29 +02:00
Andrey Prygunkov
fb8c6b9cd3 #279: size of test files
can now be adjusted via pytest.ini; reduced default sizes to speed up
tests.
2016-09-19 22:04:49 +02:00
Andrey Prygunkov
bc98b0582c #279: corrected nntp error reporting 2016-09-19 21:44:38 +02:00
guaguasi
84388785e7 #276 , #280: highlight selected rows with alternative colors 2016-09-19 06:43:07 +02:00
Andrey Prygunkov
4c519cf646 #279: fixed compilation error on Windows 2016-09-17 23:45:03 +02:00
Andrey Prygunkov
f28c049c12 fixed #284: API-method "append" and dupe check
method “append” returns nzb-id also for items added straight to history.
2016-09-17 20:38:57 +02:00
Andrey Prygunkov
33dbbcb0b5 Merge branch '279-nserv' into develop 2016-09-17 13:49:45 +02:00
Andrey Prygunkov
7f6cbd137f #279: updated .gitignore 2016-09-17 13:49:09 +02:00
Andrey Prygunkov
facd8cd41f #279: fixed file permissions 2016-09-17 13:37:56 +02:00
guaguasi
e269d8f062 #29, #278: additional options in "custom pause dialog" 2016-09-16 06:40:55 +02:00
Andrey Prygunkov
dd141e4cf5 #279: initial collection of functional tests 2016-09-13 22:05:10 +02:00
Andrey Prygunkov
f18ee92a23 #279: built-in nntp server for functional testing
- implemented nserv - built-in simple nntp server to be used for
functional testing;
- nserv is part of nzbget executable and can be easily used on all
platforms;
- to start nzbget in nserv mode use command “nzbget --nserv”.
2016-09-13 21:31:20 +02:00
Andrey Prygunkov
b25a88683b #271: do not close files on daemonizing 2016-09-09 20:12:10 +02:00
guaguasi
9dc2b8c71b #49, #260: fields containing passwords are now password fields
- fields containing passwords are now password fields with option to view;
- excluding post-processing/archive password.
2016-09-09 19:43:46 +02:00
Andrey Prygunkov
2ef393531a updated version string to 18.0-testing 2016-09-09 19:45:46 +02:00
206 changed files with 13861 additions and 3765 deletions

3
.gitignore vendored Executable file → Normal file
View File

@@ -61,3 +61,6 @@ ipch/
# NZBGet specific
nzbget
code_revision.cpp
*.temp
*.pyc
pytest.ini

View File

@@ -4,6 +4,7 @@ language: cpp
matrix:
include:
- compiler: gcc
addons:
apt:
@@ -11,7 +12,10 @@ matrix:
- ubuntu-toolchain-r-test
packages:
- g++-5
env: COMPILER=g++-5
- unrar
env:
- COMPILER=g++-5
- compiler: gcc
addons:
apt:
@@ -19,7 +23,20 @@ matrix:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
env: COMPILER=g++-4.9
- unrar
env:
- COMPILER=g++-4.9
- compiler: gcc
addons:
apt:
packages:
- unrar
env:
- COMPILER=g++-4.8
- CXXFLAGS="-std=c++11 -O2 -s"
- CONFIGUREOPTS="--disable-cpp-check"
- compiler: clang
addons:
apt:
@@ -28,9 +45,11 @@ matrix:
- llvm-toolchain-precise-3.6
packages:
- clang-3.6
env: COMPILER=clang++-3.6
- unrar
env:
- COMPILER=clang++-3.6
script:
- $COMPILER --version
- CXX=$COMPILER ./configure --enable-tests && make
- ./nzbget --tests exclude:[DupeMatcher]
- CXX=$COMPILER ./configure $CONFIGUREOPTS --enable-tests && make
- ./nzbget --tests

190
ChangeLog
View File

@@ -1,3 +1,193 @@
nzbget-19.0:
- unpack during downloading:
- downloaded files can now be unpacked as soon as every archive part is
downloaded;
- new option "DirectUnpack" to activate direct unpacking;
- direct unpack works even with obfuscated downloads; option "DirectRename"
(see below) must be active for that;
- option "ReorderFiles" (see below) should be also active for optimal file
download order;
- direct unpack works for rar-archives; 7-zip archives and simply splitted
files are processed by default unpack module;
- direct unpack obviously works only for healthy download; if download is
damaged the direct unpack cancels and the download is unpacked during
post-processing stage after files are repaired;
- direct unpack reduces the time needed to complete download and
post-processing;
- it also allows to start watching of video files during download (requires
compatible video player software);
- renaming of obfuscated file names during downloading:
- correct file names for obfuscated downloads are now determined during
download stage (instead of post-processing stage);
- downloaded files are saved into disk directly with correct names;
- direct renaming uses par2-files to restore correct file names;
- new option "DirectRename" to activate direct renaming;
- new queue-event NZB_NAMED, sent after the inner files are renamed;
- automatic reordering of files:
- inner files within nzb reordered to ensure download of files in archive
parts order;
- the files are reordered when nzb is added to queue;
- if direct renaming is active (option "DirectRename") the files are
reordered again after the correct names becomes known;
- new option "ReorderFiles";
- new command "GroupSortFiles" in api-method "editqueue";
- new subcommand "SF" of remote command "-E/--edit";
- new option "FileNaming" to control how to name obfuscated files (before they
get renamed by par-rename, rar-rename or direct-rename);
- TLS certificate verification:
- when connecting to a news server (for downloading) or a web server (for
fetching of rss feeds and nzb-files) the authenticity of the server is
validated using server security certificate. If the check fails that means
the connection cannot be trusted and must be closed with an error message
explaining the security issue;
- new options "CertCheck" and "CertStore";
- official NZBGet packages come with activated certificate check;
- when updating from an older NZBGet version the option CertCheck will be
automatically activated when the settings is saved (switch to Settings
page in web-interface and click "Save all changed");
- authentication via form in web-interface as alternative to HTTP
authentication:
- that must help with password tools having issues with HTTP authentication
dialog;
- new option "FormAuth";
- drop-downs (context menus) for priority, category and status columns:
- quicker changing of priority and category;
- easier access to actions via drop-down (context menu) in status column;
- extensions scripts can now be executed from settings page:
- script authors define custom buttons;
- when clicked the script is executed in a special mode and obtain extra
parameters;
- example script "Email.py" extended with button "Send test e-mail";
- on Windows NZBGet can now associate itself with nzb-files:
- use option in Windows installer to register NZBGet for nzb-files;
- unrar shipped within Linux package is now compiled with "fallocate" option
to improve compatibility with media players when watching videos during
downloading and unpacking;
- support for HTTP-header "X-Forwarded-For" in IP-logging;
- improvements in RSS feed view in phone mode;
- set name, password and dupe info when adding via URL by click on a button
near URL field in web-interface;
- backup-badge for items in history similar to downloads tab;
- show backup icon in history in phone theme;
- added support for ECC certificates in built-in web-server;
- save changes before performing actions in history dialog;
- proper exit code on client command success or failure.
- added host name to all error messages regarding connection issues;
- improved continuos integration with Travis CI:
- added gcc 4.8 to test matrix;
- installing unrar into test system to allow unit tests requiring unrar;
- new button "Volume Statistics" in section "News Servers" of settings page;
shows the same volume data as in global statistics dialog;
- new option "ServerX.Notes" for user comments on news servers;
- new parameters for api-method "servervolumes" as a performance optimization
measure to reduce amount of transferred data;
- new option to force connection to news servers via ipv4 or ipv6;
- removed unnecessary requests to news servers;
- updated unrar to v5.40;
- clear script execution log before executing script;
- added support for crash dumps on Windows:
- renamed option "DumpCore" to "CrashDump";
- new option "CrashTrace" to make it possible to disable default printing off
call stack in order to produce more relevant crash dumps;
- fixed: startup scheduler tasks can be executed again;
- fixed: "fatal" messages when compiling from sources.
- fixed: per-nzb download statistics could be wrong if the program was
reloaded during downloading.
- fixed crash which may happen between post-processing steps;
- fixed: asterix (*) was sometimes passed as parameter to extension scripts
(Windows only);
- fixed potential crash during update via web-interface.
nzbget-18.1:
- fixed: crash during download caused by a race condition;
- fixed: sleep mode did not work on Windows;
- fixed: queue was not saved after deleting of queued post-jobs;
- fixed: possible crash at the end of post-processing;
- fixed: "undefined" in reorder extension scripts.
nzbget-18.0:
- automatic deobfuscation of rar-archives without par-files:
- obfuscated downloads not having par-files can now be successfully
unpacked;
- also helps with downloads where rar-files were obfuscated before
creating par-files;
- new options "RarRename" and "UnpackIgnoreExt";
- multi post-processing:
- in addition to classic post-processing strategy where items are
processed one after another it is now possible to post-process
multiple items at the same time;
- new option "PostStrategy" to choose from four: sequential, balanced,
aggressive, rocket;
- in "balanced" strategy downloads needing repair do not block other
items which are processed sequentially but simultaneously with
repairing item;
- in "aggressive" mode up to three items are post-processed at the same
time and in "rocket" mode up to six items (including up to two repair
tasks);
- unified extension scripts settings:
- options "PostScript", "QueueScript", "ScanScript" and "FeedScript"
were replaced with one option "Extensions";
- users don't need to know the technical details os extension scripts as
all scripts are now can be selected at one place;
- easier activation of complex extension scripts which previously needed
to be selected in multiple options for their proper work;
- reordering download queue with drag and drop in web-interface:
- new actions "GroupMoveBefore" and "GroupMoveAfter" in API-method
"editqueue";
- priorities are now displayed as a column instead of badge; that makes it
possible to manually sort on priority;
- removed vertical lines in tables; looks better in combination with new
priority column;
- keyboard shortcuts in web-interface;
- improved UI to prevent accidental deletion of many items:
- visual indication of records selected on other pages;
- extra warning when deleting many records from history;
- additional options in "custom pause dialog";
- better handing of damaged par2-files in par-renamer:
- if par-renamer can't load a (damaged) par2-file then another par2-file
is downloaded and par-renamer tries again;
- reverted non-strict par2-filename matching to handle article subjects
with non-parseable filenames;
- better handling of obfuscated par-files;
- splitted option "Retries" into "ArticleRetries" and "UrlRetries"; option
"RetryInterval" into "ArticleInterval" and "UrlInterval";
- scheduler tasks can be started at program launch:
- use asterisk as TaskX.Time;
- graceful termination of scheduler scripts:
- scripts receive signal SIGINT (CTRL+BREAK on Windows) before
termination;
- added support for nZEDb attributes in rss feeds;
- better cleanup handling: if parameter "unpack" is disabled for an nzb-file
the cleanup isn't performed for it;
- fields containing passwords are now displayed as protected fields;
- showing password-badge for nzbs with passwords;
- allow control of what tab is shown when opening web-interface:
add "#downloads", "#history", "#messages" or "#settings" to the URL,
for example "http://localhost:6789/#history" or
"http://localhost:6789/index.html#history";
- functional testing to ensure program quality:
- implemented built-in simple nntp server to be used for functional
testing;
- created a number of tests;
- new features come with additional tests;
- improved API-method "append" in combination with duplicate check; method
returns nzb-id also for items added straight to history;
- removed parameter "offset" from api-method "editqueue":
- when needed the "offset" is now passed within parameter "Args" as
string;
- old method signature is supported for compatibility;
- improved error reporting on feed parse errors;
- highlighting selected rows with alternative colors;
- improved selecting of par2-file for repair;
- splitted config section "Download Queue" and moved many options into new
section "Connection";
- disabled SSLv3 in built-in web-server;
- multiple recipients in the example pp-script "EMail.py";
- added compatibility with openssl 1.1.0;
- fixed TLS handshake error when using GnuTLS;
- fixed: sorting of selected items may give wrong results;
- fixed: search box filter in feed view were not reset.
nzbget-17.1:
- adjustments and fixes for "Retry failed articles" function, better handling
of certain corner cases;

View File

@@ -28,6 +28,8 @@ nzbget_SOURCES = \
daemon/connect/WebDownloader.h \
daemon/extension/FeedScript.cpp \
daemon/extension/FeedScript.h \
daemon/extension/CommandScript.cpp \
daemon/extension/CommandScript.h \
daemon/extension/NzbScript.cpp \
daemon/extension/NzbScript.h \
daemon/extension/PostScript.cpp \
@@ -90,16 +92,26 @@ nzbget_SOURCES = \
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/PrePostProcessor.cpp \
daemon/postprocess/PrePostProcessor.h \
daemon/postprocess/RarRenamer.cpp \
daemon/postprocess/RarRenamer.h \
daemon/postprocess/RarReader.cpp \
daemon/postprocess/RarReader.h \
daemon/postprocess/Rename.cpp \
daemon/postprocess/Rename.h \
daemon/postprocess/Repair.cpp \
daemon/postprocess/Repair.h \
daemon/postprocess/Unpack.cpp \
daemon/postprocess/Unpack.h \
daemon/postprocess/DirectUnpack.cpp \
daemon/postprocess/DirectUnpack.h \
daemon/queue/DirectRenamer.cpp \
daemon/queue/DirectRenamer.h \
daemon/queue/DiskState.cpp \
daemon/queue/DiskState.h \
daemon/queue/DownloadInfo.cpp \
@@ -146,6 +158,16 @@ nzbget_SOURCES = \
daemon/util/FileSystem.h \
daemon/util/Util.cpp \
daemon/util/Util.h \
daemon/nserv/NServMain.h \
daemon/nserv/NServMain.cpp \
daemon/nserv/NServFrontend.h \
daemon/nserv/NServFrontend.cpp \
daemon/nserv/NntpServer.h \
daemon/nserv/NntpServer.cpp \
daemon/nserv/NzbGenerator.h \
daemon/nserv/NzbGenerator.cpp \
daemon/nserv/YEncoder.h \
daemon/nserv/YEncoder.cpp \
code_revision.cpp
if WITH_PAR2
@@ -174,8 +196,6 @@ nzbget_SOURCES += \
lib/par2/md5.cpp \
lib/par2/md5.h \
lib/par2/par2cmdline.h \
lib/par2/par2creatorsourcefile.cpp \
lib/par2/par2creatorsourcefile.h \
lib/par2/par2fileformat.cpp \
lib/par2/par2fileformat.h \
lib/par2/par2repairer.cpp \
@@ -205,6 +225,7 @@ AM_CPPFLAGS = \
-I$(srcdir)/daemon/queue \
-I$(srcdir)/daemon/remote \
-I$(srcdir)/daemon/util \
-I$(srcdir)/daemon/nserv \
-I$(srcdir)/lib/par2
if WITH_TESTS
@@ -218,6 +239,9 @@ nzbget_SOURCES += \
tests/main/OptionsTest.cpp \
tests/feed/FeedFilterTest.cpp \
tests/postprocess/DupeMatcherTest.cpp \
tests/postprocess/RarRenamerTest.cpp \
tests/postprocess/RarReaderTest.cpp \
tests/postprocess/DirectUnpackTest.cpp \
tests/queue/NzbFileTest.cpp \
tests/nntp/ServerPoolTest.cpp \
tests/util/FileSystemTest.cpp \
@@ -366,7 +390,28 @@ testdata_FILES = \
tests/testdata/parchecker/testfile.par2 \
tests/testdata/parchecker/testfile.vol00+1.PAR2 \
tests/testdata/parchecker/testfile.vol01+2.PAR2 \
tests/testdata/parchecker/testfile.vol03+3.PAR2
tests/testdata/parchecker/testfile.vol03+3.PAR2 \
tests/testdata/rarrenamer/testfile3.part01.rar \
tests/testdata/rarrenamer/testfile3.part02.rar \
tests/testdata/rarrenamer/testfile3.part03.rar \
tests/testdata/rarrenamer/testfile5.part01.rar \
tests/testdata/rarrenamer/testfile5.part02.rar \
tests/testdata/rarrenamer/testfile5.part03.rar \
tests/testdata/rarrenamer/testfile3oldnam.rar \
tests/testdata/rarrenamer/testfile3oldnam.r00 \
tests/testdata/rarrenamer/testfile3oldnam.r01 \
tests/testdata/rarrenamer/testfile3encdata.part01.rar \
tests/testdata/rarrenamer/testfile3encdata.part02.rar \
tests/testdata/rarrenamer/testfile3encdata.part03.rar \
tests/testdata/rarrenamer/testfile3encnam.part01.rar \
tests/testdata/rarrenamer/testfile3encnam.part02.rar \
tests/testdata/rarrenamer/testfile3encnam.part03.rar \
tests/testdata/rarrenamer/testfile5encdata.part01.rar \
tests/testdata/rarrenamer/testfile5encdata.part02.rar \
tests/testdata/rarrenamer/testfile5encdata.part03.rar \
tests/testdata/rarrenamer/testfile5encnam.part01.rar \
tests/testdata/rarrenamer/testfile5encnam.part02.rar \
tests/testdata/rarrenamer/testfile5encnam.part03.rar
# Install
dist_doc_DATA = $(doc_FILES)
@@ -417,21 +462,21 @@ uninstall-conf:
# 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)" ; \
B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p'`; \
M=`git status --porcelain` ; \
if test "$$M" != "" ; then \
M="M" ; \
fi ; \
if test "$$B" = "master" ; then \
V="$$M" ; \
elif test "$$B" = "develop" ; then \
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
V=`git rev-list HEAD | wc -l | xargs` ; \
V="$${V}$$M" ; \
else \
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
V=`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)"; \
H=`test -f ./code_revision.cpp && head -n 1 code_revision.cpp`; \
if test "/* $$V */" != "$$H" ; then \
( \
echo "/* $$V */" ;\

495
Makefile.in vendored
View File

@@ -84,8 +84,6 @@ bin_PROGRAMS = nzbget$(EXEEXT)
@WITH_PAR2_TRUE@ lib/par2/md5.cpp \
@WITH_PAR2_TRUE@ lib/par2/md5.h \
@WITH_PAR2_TRUE@ lib/par2/par2cmdline.h \
@WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.cpp \
@WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.h \
@WITH_PAR2_TRUE@ lib/par2/par2fileformat.cpp \
@WITH_PAR2_TRUE@ lib/par2/par2fileformat.h \
@WITH_PAR2_TRUE@ lib/par2/par2repairer.cpp \
@@ -112,16 +110,21 @@ bin_PROGRAMS = nzbget$(EXEEXT)
@WITH_TESTS_TRUE@ tests/main/CommandLineParserTest.cpp \
@WITH_TESTS_TRUE@ tests/main/OptionsTest.cpp \
@WITH_TESTS_TRUE@ tests/feed/FeedFilterTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/ParCheckerTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/ParRenamerTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/DupeMatcherTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/RarRenamerTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/RarReaderTest.cpp \
@WITH_TESTS_TRUE@ tests/postprocess/DirectUnpackTest.cpp \
@WITH_TESTS_TRUE@ tests/queue/NzbFileTest.cpp \
@WITH_TESTS_TRUE@ tests/nntp/ServerPoolTest.cpp \
@WITH_TESTS_TRUE@ tests/util/FileSystemTest.cpp \
@WITH_TESTS_TRUE@ tests/util/NStringTest.cpp \
@WITH_TESTS_TRUE@ tests/util/UtilTest.cpp
@WITH_TESTS_TRUE@am__append_3 = \
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@am__append_3 = \
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ tests/postprocess/ParCheckerTest.cpp \
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ tests/postprocess/ParRenamerTest.cpp
@WITH_TESTS_TRUE@am__append_4 = \
@WITH_TESTS_TRUE@ -I$(srcdir)/lib/catch \
@WITH_TESTS_TRUE@ -I$(srcdir)/tests/suite
@@ -151,9 +154,12 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \
daemon/connect/Connection.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/FeedScript.h \
daemon/extension/CommandScript.cpp \
daemon/extension/CommandScript.h \
daemon/extension/NzbScript.cpp daemon/extension/NzbScript.h \
daemon/extension/PostScript.cpp daemon/extension/PostScript.h \
daemon/extension/QueueScript.cpp \
daemon/extension/QueueScript.h daemon/extension/ScanScript.cpp \
daemon/extension/ScanScript.h \
daemon/extension/SchedulerScript.cpp \
@@ -191,15 +197,22 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.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/PrePostProcessor.cpp \
daemon/postprocess/PrePostProcessor.h \
daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \
daemon/postprocess/RarRenamer.cpp \
daemon/postprocess/RarRenamer.h \
daemon/postprocess/RarReader.cpp \
daemon/postprocess/RarReader.h daemon/postprocess/Rename.cpp \
daemon/postprocess/Rename.h daemon/postprocess/Repair.cpp \
daemon/postprocess/Repair.h daemon/postprocess/Unpack.cpp \
daemon/postprocess/Unpack.h \
daemon/postprocess/DirectUnpack.cpp \
daemon/postprocess/DirectUnpack.h \
daemon/queue/DirectRenamer.cpp daemon/queue/DirectRenamer.h \
daemon/queue/DiskState.cpp daemon/queue/DiskState.h \
daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \
daemon/queue/DupeCoordinator.cpp \
@@ -223,21 +236,26 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \
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 code_revision.cpp \
lib/par2/commandline.cpp lib/par2/commandline.h \
lib/par2/crc.cpp lib/par2/crc.h lib/par2/creatorpacket.cpp \
lib/par2/creatorpacket.h lib/par2/criticalpacket.cpp \
lib/par2/criticalpacket.h lib/par2/datablock.cpp \
lib/par2/datablock.h lib/par2/descriptionpacket.cpp \
lib/par2/descriptionpacket.h lib/par2/diskfile.cpp \
lib/par2/diskfile.h lib/par2/filechecksummer.cpp \
lib/par2/filechecksummer.h lib/par2/galois.cpp \
lib/par2/galois.h lib/par2/letype.h lib/par2/mainpacket.cpp \
lib/par2/mainpacket.h lib/par2/md5.cpp lib/par2/md5.h \
lib/par2/par2cmdline.h lib/par2/par2creatorsourcefile.cpp \
lib/par2/par2creatorsourcefile.h lib/par2/par2fileformat.cpp \
lib/par2/par2fileformat.h lib/par2/par2repairer.cpp \
lib/par2/par2repairer.h lib/par2/par2repairersourcefile.cpp \
daemon/util/Util.cpp daemon/util/Util.h \
daemon/nserv/NServMain.h daemon/nserv/NServMain.cpp \
daemon/nserv/NServFrontend.h daemon/nserv/NServFrontend.cpp \
daemon/nserv/NntpServer.h daemon/nserv/NntpServer.cpp \
daemon/nserv/NzbGenerator.h daemon/nserv/NzbGenerator.cpp \
daemon/nserv/YEncoder.h daemon/nserv/YEncoder.cpp \
code_revision.cpp lib/par2/commandline.cpp \
lib/par2/commandline.h lib/par2/crc.cpp lib/par2/crc.h \
lib/par2/creatorpacket.cpp lib/par2/creatorpacket.h \
lib/par2/criticalpacket.cpp lib/par2/criticalpacket.h \
lib/par2/datablock.cpp lib/par2/datablock.h \
lib/par2/descriptionpacket.cpp lib/par2/descriptionpacket.h \
lib/par2/diskfile.cpp lib/par2/diskfile.h \
lib/par2/filechecksummer.cpp lib/par2/filechecksummer.h \
lib/par2/galois.cpp lib/par2/galois.h lib/par2/letype.h \
lib/par2/mainpacket.cpp lib/par2/mainpacket.h lib/par2/md5.cpp \
lib/par2/md5.h lib/par2/par2cmdline.h \
lib/par2/par2fileformat.cpp lib/par2/par2fileformat.h \
lib/par2/par2repairer.cpp lib/par2/par2repairer.h \
lib/par2/par2repairersourcefile.cpp \
lib/par2/par2repairersourcefile.h lib/par2/parheaders.cpp \
lib/par2/parheaders.h lib/par2/recoverypacket.cpp \
lib/par2/recoverypacket.h lib/par2/reedsolomon.cpp \
@@ -248,19 +266,20 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.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/postprocess/RarRenamerTest.cpp \
tests/postprocess/RarReaderTest.cpp \
tests/postprocess/DirectUnpackTest.cpp \
tests/queue/NzbFileTest.cpp tests/nntp/ServerPoolTest.cpp \
tests/util/FileSystemTest.cpp tests/util/NStringTest.cpp \
tests/util/UtilTest.cpp
tests/util/UtilTest.cpp tests/postprocess/ParCheckerTest.cpp \
tests/postprocess/ParRenamerTest.cpp
@WITH_PAR2_TRUE@am__objects_1 = commandline.$(OBJEXT) crc.$(OBJEXT) \
@WITH_PAR2_TRUE@ creatorpacket.$(OBJEXT) \
@WITH_PAR2_TRUE@ criticalpacket.$(OBJEXT) datablock.$(OBJEXT) \
@WITH_PAR2_TRUE@ descriptionpacket.$(OBJEXT) diskfile.$(OBJEXT) \
@WITH_PAR2_TRUE@ filechecksummer.$(OBJEXT) galois.$(OBJEXT) \
@WITH_PAR2_TRUE@ mainpacket.$(OBJEXT) md5.$(OBJEXT) \
@WITH_PAR2_TRUE@ par2creatorsourcefile.$(OBJEXT) \
@WITH_PAR2_TRUE@ par2fileformat.$(OBJEXT) \
@WITH_PAR2_TRUE@ par2repairer.$(OBJEXT) \
@WITH_PAR2_TRUE@ par2repairersourcefile.$(OBJEXT) \
@@ -272,16 +291,21 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \
@WITH_TESTS_TRUE@ CommandLineParserTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ OptionsTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ FeedFilterTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ ParCheckerTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ ParRenamerTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ DupeMatcherTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ RarRenamerTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ RarReaderTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ DirectUnpackTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ NzbFileTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ ServerPoolTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ FileSystemTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ NStringTest.$(OBJEXT) UtilTest.$(OBJEXT)
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@am__objects_3 = \
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ ParCheckerTest.$(OBJEXT) \
@WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ ParRenamerTest.$(OBJEXT)
am_nzbget_OBJECTS = Connection.$(OBJEXT) TlsSocket.$(OBJEXT) \
WebDownloader.$(OBJEXT) FeedScript.$(OBJEXT) \
NzbScript.$(OBJEXT) PostScript.$(OBJEXT) QueueScript.$(OBJEXT) \
CommandScript.$(OBJEXT) NzbScript.$(OBJEXT) \
PostScript.$(OBJEXT) QueueScript.$(OBJEXT) \
ScanScript.$(OBJEXT) SchedulerScript.$(OBJEXT) \
ScriptConfig.$(OBJEXT) FeedCoordinator.$(OBJEXT) \
FeedFile.$(OBJEXT) FeedFilter.$(OBJEXT) FeedInfo.$(OBJEXT) \
@@ -294,20 +318,23 @@ am_nzbget_OBJECTS = Connection.$(OBJEXT) TlsSocket.$(OBJEXT) \
Decoder.$(OBJEXT) NewsServer.$(OBJEXT) \
NntpConnection.$(OBJEXT) ServerPool.$(OBJEXT) \
StatMeter.$(OBJEXT) Cleanup.$(OBJEXT) DupeMatcher.$(OBJEXT) \
ParChecker.$(OBJEXT) ParCoordinator.$(OBJEXT) \
ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \
PrePostProcessor.$(OBJEXT) Unpack.$(OBJEXT) \
DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
DupeCoordinator.$(OBJEXT) HistoryCoordinator.$(OBJEXT) \
NzbFile.$(OBJEXT) QueueCoordinator.$(OBJEXT) \
QueueEditor.$(OBJEXT) Scanner.$(OBJEXT) \
UrlCoordinator.$(OBJEXT) BinRpc.$(OBJEXT) \
ParChecker.$(OBJEXT) ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \
PrePostProcessor.$(OBJEXT) RarRenamer.$(OBJEXT) \
RarReader.$(OBJEXT) Rename.$(OBJEXT) Repair.$(OBJEXT) \
Unpack.$(OBJEXT) DirectUnpack.$(OBJEXT) \
DirectRenamer.$(OBJEXT) DiskState.$(OBJEXT) \
DownloadInfo.$(OBJEXT) DupeCoordinator.$(OBJEXT) \
HistoryCoordinator.$(OBJEXT) NzbFile.$(OBJEXT) \
QueueCoordinator.$(OBJEXT) QueueEditor.$(OBJEXT) \
Scanner.$(OBJEXT) UrlCoordinator.$(OBJEXT) BinRpc.$(OBJEXT) \
RemoteClient.$(OBJEXT) RemoteServer.$(OBJEXT) \
WebServer.$(OBJEXT) XmlRpc.$(OBJEXT) Log.$(OBJEXT) \
NString.$(OBJEXT) Observer.$(OBJEXT) Script.$(OBJEXT) \
Thread.$(OBJEXT) Service.$(OBJEXT) FileSystem.$(OBJEXT) \
Util.$(OBJEXT) code_revision.$(OBJEXT) $(am__objects_1) \
$(am__objects_2)
Util.$(OBJEXT) NServMain.$(OBJEXT) NServFrontend.$(OBJEXT) \
NntpServer.$(OBJEXT) NzbGenerator.$(OBJEXT) YEncoder.$(OBJEXT) \
code_revision.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
$(am__objects_3)
nzbget_OBJECTS = $(am_nzbget_OBJECTS)
nzbget_LDADD = $(LDADD)
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
@@ -443,6 +470,8 @@ mandir = @mandir@
mkdir_p = @mkdir_p@
ncurses_CFLAGS = @ncurses_CFLAGS@
ncurses_LIBS = @ncurses_LIBS@
nettle_CFLAGS = @nettle_CFLAGS@
nettle_LIBS = @nettle_LIBS@
oldincludedir = @oldincludedir@
openssl_CFLAGS = @openssl_CFLAGS@
openssl_LIBS = @openssl_LIBS@
@@ -464,9 +493,12 @@ nzbget_SOURCES = daemon/connect/Connection.cpp \
daemon/connect/Connection.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/FeedScript.h \
daemon/extension/CommandScript.cpp \
daemon/extension/CommandScript.h \
daemon/extension/NzbScript.cpp daemon/extension/NzbScript.h \
daemon/extension/PostScript.cpp daemon/extension/PostScript.h \
daemon/extension/QueueScript.cpp \
daemon/extension/QueueScript.h daemon/extension/ScanScript.cpp \
daemon/extension/ScanScript.h \
daemon/extension/SchedulerScript.cpp \
@@ -504,15 +536,22 @@ nzbget_SOURCES = daemon/connect/Connection.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/PrePostProcessor.cpp \
daemon/postprocess/PrePostProcessor.h \
daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \
daemon/postprocess/RarRenamer.cpp \
daemon/postprocess/RarRenamer.h \
daemon/postprocess/RarReader.cpp \
daemon/postprocess/RarReader.h daemon/postprocess/Rename.cpp \
daemon/postprocess/Rename.h daemon/postprocess/Repair.cpp \
daemon/postprocess/Repair.h daemon/postprocess/Unpack.cpp \
daemon/postprocess/Unpack.h \
daemon/postprocess/DirectUnpack.cpp \
daemon/postprocess/DirectUnpack.h \
daemon/queue/DirectRenamer.cpp daemon/queue/DirectRenamer.h \
daemon/queue/DiskState.cpp daemon/queue/DiskState.h \
daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \
daemon/queue/DupeCoordinator.cpp \
@@ -536,14 +575,20 @@ nzbget_SOURCES = daemon/connect/Connection.cpp \
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 code_revision.cpp \
$(am__append_1) $(am__append_2)
daemon/util/Util.cpp daemon/util/Util.h \
daemon/nserv/NServMain.h daemon/nserv/NServMain.cpp \
daemon/nserv/NServFrontend.h daemon/nserv/NServFrontend.cpp \
daemon/nserv/NntpServer.h daemon/nserv/NntpServer.cpp \
daemon/nserv/NzbGenerator.h daemon/nserv/NzbGenerator.cpp \
daemon/nserv/YEncoder.h daemon/nserv/YEncoder.cpp \
code_revision.cpp $(am__append_1) $(am__append_2) \
$(am__append_3)
AM_CPPFLAGS = -I$(srcdir)/daemon/connect -I$(srcdir)/daemon/extension \
-I$(srcdir)/daemon/feed -I$(srcdir)/daemon/frontend \
-I$(srcdir)/daemon/main -I$(srcdir)/daemon/nntp \
-I$(srcdir)/daemon/postprocess -I$(srcdir)/daemon/queue \
-I$(srcdir)/daemon/remote -I$(srcdir)/daemon/util \
-I$(srcdir)/lib/par2 $(am__append_3)
-I$(srcdir)/daemon/nserv -I$(srcdir)/lib/par2 $(am__append_4)
EXTRA_DIST = \
$(windows_FILES) \
$(osx_FILES) \
@@ -675,7 +720,28 @@ testdata_FILES = \
tests/testdata/parchecker/testfile.par2 \
tests/testdata/parchecker/testfile.vol00+1.PAR2 \
tests/testdata/parchecker/testfile.vol01+2.PAR2 \
tests/testdata/parchecker/testfile.vol03+3.PAR2
tests/testdata/parchecker/testfile.vol03+3.PAR2 \
tests/testdata/rarrenamer/testfile3.part01.rar \
tests/testdata/rarrenamer/testfile3.part02.rar \
tests/testdata/rarrenamer/testfile3.part03.rar \
tests/testdata/rarrenamer/testfile5.part01.rar \
tests/testdata/rarrenamer/testfile5.part02.rar \
tests/testdata/rarrenamer/testfile5.part03.rar \
tests/testdata/rarrenamer/testfile3oldnam.rar \
tests/testdata/rarrenamer/testfile3oldnam.r00 \
tests/testdata/rarrenamer/testfile3oldnam.r01 \
tests/testdata/rarrenamer/testfile3encdata.part01.rar \
tests/testdata/rarrenamer/testfile3encdata.part02.rar \
tests/testdata/rarrenamer/testfile3encdata.part03.rar \
tests/testdata/rarrenamer/testfile3encnam.part01.rar \
tests/testdata/rarrenamer/testfile3encnam.part02.rar \
tests/testdata/rarrenamer/testfile3encnam.part03.rar \
tests/testdata/rarrenamer/testfile5encdata.part01.rar \
tests/testdata/rarrenamer/testfile5encdata.part02.rar \
tests/testdata/rarrenamer/testfile5encdata.part03.rar \
tests/testdata/rarrenamer/testfile5encnam.part01.rar \
tests/testdata/rarrenamer/testfile5encnam.part02.rar \
tests/testdata/rarrenamer/testfile5encnam.part03.rar
# Install
@@ -812,8 +878,12 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ColoredFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CommandLineParser.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CommandLineParserTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CommandScript.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Connection.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Decoder.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DirectRenamer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DirectUnpack.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DirectUnpackTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskService.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskState.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadInfo.Po@am__quote@
@@ -834,19 +904,22 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/LoggableFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Maintenance.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NCursesFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NServFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NServMain.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NString.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NStringTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NewsServer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NntpConnection.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NntpServer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbFile.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbFileTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbGenerator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbScript.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Observer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Options.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/OptionsTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParChecker.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCheckerTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCoordinator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParParser.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamerTest.Po@am__quote@
@@ -855,8 +928,14 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueCoordinator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueEditor.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueScript.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarReader.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarReaderTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarRenamer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarRenamerTest.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteClient.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteServer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Rename.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Repair.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScanScript.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scanner.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scheduler.Po@am__quote@
@@ -879,6 +958,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebDownloader.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebServer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XmlRpc.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/YEncoder.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/code_revision.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/commandline.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc.Po@am__quote@
@@ -892,7 +972,6 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainpacket.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nzbget.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2creatorsourcefile.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2fileformat.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairersourcefile.Po@am__quote@
@@ -972,6 +1051,20 @@ FeedScript.obj: daemon/extension/FeedScript.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedScript.obj `if test -f 'daemon/extension/FeedScript.cpp'; then $(CYGPATH_W) 'daemon/extension/FeedScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/FeedScript.cpp'; fi`
CommandScript.o: daemon/extension/CommandScript.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandScript.o -MD -MP -MF "$(DEPDIR)/CommandScript.Tpo" -c -o CommandScript.o `test -f 'daemon/extension/CommandScript.cpp' || echo '$(srcdir)/'`daemon/extension/CommandScript.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandScript.Tpo" "$(DEPDIR)/CommandScript.Po"; else rm -f "$(DEPDIR)/CommandScript.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/CommandScript.cpp' object='CommandScript.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandScript.o `test -f 'daemon/extension/CommandScript.cpp' || echo '$(srcdir)/'`daemon/extension/CommandScript.cpp
CommandScript.obj: daemon/extension/CommandScript.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandScript.obj -MD -MP -MF "$(DEPDIR)/CommandScript.Tpo" -c -o CommandScript.obj `if test -f 'daemon/extension/CommandScript.cpp'; then $(CYGPATH_W) 'daemon/extension/CommandScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/CommandScript.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandScript.Tpo" "$(DEPDIR)/CommandScript.Po"; else rm -f "$(DEPDIR)/CommandScript.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/CommandScript.cpp' object='CommandScript.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandScript.obj `if test -f 'daemon/extension/CommandScript.cpp'; then $(CYGPATH_W) 'daemon/extension/CommandScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/CommandScript.cpp'; fi`
NzbScript.o: daemon/extension/NzbScript.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbScript.o -MD -MP -MF "$(DEPDIR)/NzbScript.Tpo" -c -o NzbScript.o `test -f 'daemon/extension/NzbScript.cpp' || echo '$(srcdir)/'`daemon/extension/NzbScript.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbScript.Tpo" "$(DEPDIR)/NzbScript.Po"; else rm -f "$(DEPDIR)/NzbScript.Tpo"; exit 1; fi
@@ -1406,20 +1499,6 @@ ParChecker.obj: daemon/postprocess/ParChecker.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParChecker.obj `if test -f 'daemon/postprocess/ParChecker.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParChecker.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParChecker.cpp'; fi`
ParCoordinator.o: daemon/postprocess/ParCoordinator.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.o -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp
ParCoordinator.obj: daemon/postprocess/ParCoordinator.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.obj -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi`
ParParser.o: daemon/postprocess/ParParser.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParParser.o -MD -MP -MF "$(DEPDIR)/ParParser.Tpo" -c -o ParParser.o `test -f 'daemon/postprocess/ParParser.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParParser.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParParser.Tpo" "$(DEPDIR)/ParParser.Po"; else rm -f "$(DEPDIR)/ParParser.Tpo"; exit 1; fi
@@ -1462,6 +1541,62 @@ PrePostProcessor.obj: daemon/postprocess/PrePostProcessor.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PrePostProcessor.obj `if test -f 'daemon/postprocess/PrePostProcessor.cpp'; then $(CYGPATH_W) 'daemon/postprocess/PrePostProcessor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/PrePostProcessor.cpp'; fi`
RarRenamer.o: daemon/postprocess/RarRenamer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamer.o -MD -MP -MF "$(DEPDIR)/RarRenamer.Tpo" -c -o RarRenamer.o `test -f 'daemon/postprocess/RarRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarRenamer.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamer.Tpo" "$(DEPDIR)/RarRenamer.Po"; else rm -f "$(DEPDIR)/RarRenamer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarRenamer.cpp' object='RarRenamer.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamer.o `test -f 'daemon/postprocess/RarRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarRenamer.cpp
RarRenamer.obj: daemon/postprocess/RarRenamer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamer.obj -MD -MP -MF "$(DEPDIR)/RarRenamer.Tpo" -c -o RarRenamer.obj `if test -f 'daemon/postprocess/RarRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarRenamer.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamer.Tpo" "$(DEPDIR)/RarRenamer.Po"; else rm -f "$(DEPDIR)/RarRenamer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarRenamer.cpp' object='RarRenamer.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamer.obj `if test -f 'daemon/postprocess/RarRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarRenamer.cpp'; fi`
RarReader.o: daemon/postprocess/RarReader.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReader.o -MD -MP -MF "$(DEPDIR)/RarReader.Tpo" -c -o RarReader.o `test -f 'daemon/postprocess/RarReader.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarReader.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReader.Tpo" "$(DEPDIR)/RarReader.Po"; else rm -f "$(DEPDIR)/RarReader.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarReader.cpp' object='RarReader.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReader.o `test -f 'daemon/postprocess/RarReader.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarReader.cpp
RarReader.obj: daemon/postprocess/RarReader.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReader.obj -MD -MP -MF "$(DEPDIR)/RarReader.Tpo" -c -o RarReader.obj `if test -f 'daemon/postprocess/RarReader.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarReader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarReader.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReader.Tpo" "$(DEPDIR)/RarReader.Po"; else rm -f "$(DEPDIR)/RarReader.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarReader.cpp' object='RarReader.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReader.obj `if test -f 'daemon/postprocess/RarReader.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarReader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarReader.cpp'; fi`
Rename.o: daemon/postprocess/Rename.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Rename.o -MD -MP -MF "$(DEPDIR)/Rename.Tpo" -c -o Rename.o `test -f 'daemon/postprocess/Rename.cpp' || echo '$(srcdir)/'`daemon/postprocess/Rename.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Rename.Tpo" "$(DEPDIR)/Rename.Po"; else rm -f "$(DEPDIR)/Rename.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Rename.cpp' object='Rename.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Rename.o `test -f 'daemon/postprocess/Rename.cpp' || echo '$(srcdir)/'`daemon/postprocess/Rename.cpp
Rename.obj: daemon/postprocess/Rename.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Rename.obj -MD -MP -MF "$(DEPDIR)/Rename.Tpo" -c -o Rename.obj `if test -f 'daemon/postprocess/Rename.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Rename.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Rename.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Rename.Tpo" "$(DEPDIR)/Rename.Po"; else rm -f "$(DEPDIR)/Rename.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Rename.cpp' object='Rename.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Rename.obj `if test -f 'daemon/postprocess/Rename.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Rename.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Rename.cpp'; fi`
Repair.o: daemon/postprocess/Repair.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Repair.o -MD -MP -MF "$(DEPDIR)/Repair.Tpo" -c -o Repair.o `test -f 'daemon/postprocess/Repair.cpp' || echo '$(srcdir)/'`daemon/postprocess/Repair.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Repair.Tpo" "$(DEPDIR)/Repair.Po"; else rm -f "$(DEPDIR)/Repair.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Repair.cpp' object='Repair.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Repair.o `test -f 'daemon/postprocess/Repair.cpp' || echo '$(srcdir)/'`daemon/postprocess/Repair.cpp
Repair.obj: daemon/postprocess/Repair.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Repair.obj -MD -MP -MF "$(DEPDIR)/Repair.Tpo" -c -o Repair.obj `if test -f 'daemon/postprocess/Repair.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Repair.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Repair.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Repair.Tpo" "$(DEPDIR)/Repair.Po"; else rm -f "$(DEPDIR)/Repair.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Repair.cpp' object='Repair.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Repair.obj `if test -f 'daemon/postprocess/Repair.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Repair.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Repair.cpp'; fi`
Unpack.o: daemon/postprocess/Unpack.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Unpack.o -MD -MP -MF "$(DEPDIR)/Unpack.Tpo" -c -o Unpack.o `test -f 'daemon/postprocess/Unpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/Unpack.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Unpack.Tpo" "$(DEPDIR)/Unpack.Po"; else rm -f "$(DEPDIR)/Unpack.Tpo"; exit 1; fi
@@ -1476,6 +1611,34 @@ Unpack.obj: daemon/postprocess/Unpack.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Unpack.obj `if test -f 'daemon/postprocess/Unpack.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Unpack.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Unpack.cpp'; fi`
DirectUnpack.o: daemon/postprocess/DirectUnpack.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectUnpack.o -MD -MP -MF "$(DEPDIR)/DirectUnpack.Tpo" -c -o DirectUnpack.o `test -f 'daemon/postprocess/DirectUnpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/DirectUnpack.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectUnpack.Tpo" "$(DEPDIR)/DirectUnpack.Po"; else rm -f "$(DEPDIR)/DirectUnpack.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/DirectUnpack.cpp' object='DirectUnpack.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectUnpack.o `test -f 'daemon/postprocess/DirectUnpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/DirectUnpack.cpp
DirectUnpack.obj: daemon/postprocess/DirectUnpack.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectUnpack.obj -MD -MP -MF "$(DEPDIR)/DirectUnpack.Tpo" -c -o DirectUnpack.obj `if test -f 'daemon/postprocess/DirectUnpack.cpp'; then $(CYGPATH_W) 'daemon/postprocess/DirectUnpack.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/DirectUnpack.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectUnpack.Tpo" "$(DEPDIR)/DirectUnpack.Po"; else rm -f "$(DEPDIR)/DirectUnpack.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/DirectUnpack.cpp' object='DirectUnpack.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectUnpack.obj `if test -f 'daemon/postprocess/DirectUnpack.cpp'; then $(CYGPATH_W) 'daemon/postprocess/DirectUnpack.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/DirectUnpack.cpp'; fi`
DirectRenamer.o: daemon/queue/DirectRenamer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectRenamer.o -MD -MP -MF "$(DEPDIR)/DirectRenamer.Tpo" -c -o DirectRenamer.o `test -f 'daemon/queue/DirectRenamer.cpp' || echo '$(srcdir)/'`daemon/queue/DirectRenamer.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectRenamer.Tpo" "$(DEPDIR)/DirectRenamer.Po"; else rm -f "$(DEPDIR)/DirectRenamer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DirectRenamer.cpp' object='DirectRenamer.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectRenamer.o `test -f 'daemon/queue/DirectRenamer.cpp' || echo '$(srcdir)/'`daemon/queue/DirectRenamer.cpp
DirectRenamer.obj: daemon/queue/DirectRenamer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectRenamer.obj -MD -MP -MF "$(DEPDIR)/DirectRenamer.Tpo" -c -o DirectRenamer.obj `if test -f 'daemon/queue/DirectRenamer.cpp'; then $(CYGPATH_W) 'daemon/queue/DirectRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DirectRenamer.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectRenamer.Tpo" "$(DEPDIR)/DirectRenamer.Po"; else rm -f "$(DEPDIR)/DirectRenamer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DirectRenamer.cpp' object='DirectRenamer.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectRenamer.obj `if test -f 'daemon/queue/DirectRenamer.cpp'; then $(CYGPATH_W) 'daemon/queue/DirectRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DirectRenamer.cpp'; fi`
DiskState.o: daemon/queue/DiskState.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DiskState.o -MD -MP -MF "$(DEPDIR)/DiskState.Tpo" -c -o DiskState.o `test -f 'daemon/queue/DiskState.cpp' || echo '$(srcdir)/'`daemon/queue/DiskState.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DiskState.Tpo" "$(DEPDIR)/DiskState.Po"; else rm -f "$(DEPDIR)/DiskState.Tpo"; exit 1; fi
@@ -1784,6 +1947,76 @@ Util.obj: daemon/util/Util.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Util.obj `if test -f 'daemon/util/Util.cpp'; then $(CYGPATH_W) 'daemon/util/Util.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Util.cpp'; fi`
NServMain.o: daemon/nserv/NServMain.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServMain.o -MD -MP -MF "$(DEPDIR)/NServMain.Tpo" -c -o NServMain.o `test -f 'daemon/nserv/NServMain.cpp' || echo '$(srcdir)/'`daemon/nserv/NServMain.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServMain.Tpo" "$(DEPDIR)/NServMain.Po"; else rm -f "$(DEPDIR)/NServMain.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServMain.cpp' object='NServMain.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServMain.o `test -f 'daemon/nserv/NServMain.cpp' || echo '$(srcdir)/'`daemon/nserv/NServMain.cpp
NServMain.obj: daemon/nserv/NServMain.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServMain.obj -MD -MP -MF "$(DEPDIR)/NServMain.Tpo" -c -o NServMain.obj `if test -f 'daemon/nserv/NServMain.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServMain.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServMain.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServMain.Tpo" "$(DEPDIR)/NServMain.Po"; else rm -f "$(DEPDIR)/NServMain.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServMain.cpp' object='NServMain.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServMain.obj `if test -f 'daemon/nserv/NServMain.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServMain.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServMain.cpp'; fi`
NServFrontend.o: daemon/nserv/NServFrontend.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServFrontend.o -MD -MP -MF "$(DEPDIR)/NServFrontend.Tpo" -c -o NServFrontend.o `test -f 'daemon/nserv/NServFrontend.cpp' || echo '$(srcdir)/'`daemon/nserv/NServFrontend.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServFrontend.Tpo" "$(DEPDIR)/NServFrontend.Po"; else rm -f "$(DEPDIR)/NServFrontend.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServFrontend.cpp' object='NServFrontend.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServFrontend.o `test -f 'daemon/nserv/NServFrontend.cpp' || echo '$(srcdir)/'`daemon/nserv/NServFrontend.cpp
NServFrontend.obj: daemon/nserv/NServFrontend.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServFrontend.obj -MD -MP -MF "$(DEPDIR)/NServFrontend.Tpo" -c -o NServFrontend.obj `if test -f 'daemon/nserv/NServFrontend.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServFrontend.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServFrontend.Tpo" "$(DEPDIR)/NServFrontend.Po"; else rm -f "$(DEPDIR)/NServFrontend.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServFrontend.cpp' object='NServFrontend.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServFrontend.obj `if test -f 'daemon/nserv/NServFrontend.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServFrontend.cpp'; fi`
NntpServer.o: daemon/nserv/NntpServer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NntpServer.o -MD -MP -MF "$(DEPDIR)/NntpServer.Tpo" -c -o NntpServer.o `test -f 'daemon/nserv/NntpServer.cpp' || echo '$(srcdir)/'`daemon/nserv/NntpServer.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NntpServer.Tpo" "$(DEPDIR)/NntpServer.Po"; else rm -f "$(DEPDIR)/NntpServer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NntpServer.cpp' object='NntpServer.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NntpServer.o `test -f 'daemon/nserv/NntpServer.cpp' || echo '$(srcdir)/'`daemon/nserv/NntpServer.cpp
NntpServer.obj: daemon/nserv/NntpServer.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NntpServer.obj -MD -MP -MF "$(DEPDIR)/NntpServer.Tpo" -c -o NntpServer.obj `if test -f 'daemon/nserv/NntpServer.cpp'; then $(CYGPATH_W) 'daemon/nserv/NntpServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NntpServer.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NntpServer.Tpo" "$(DEPDIR)/NntpServer.Po"; else rm -f "$(DEPDIR)/NntpServer.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NntpServer.cpp' object='NntpServer.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NntpServer.obj `if test -f 'daemon/nserv/NntpServer.cpp'; then $(CYGPATH_W) 'daemon/nserv/NntpServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NntpServer.cpp'; fi`
NzbGenerator.o: daemon/nserv/NzbGenerator.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbGenerator.o -MD -MP -MF "$(DEPDIR)/NzbGenerator.Tpo" -c -o NzbGenerator.o `test -f 'daemon/nserv/NzbGenerator.cpp' || echo '$(srcdir)/'`daemon/nserv/NzbGenerator.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbGenerator.Tpo" "$(DEPDIR)/NzbGenerator.Po"; else rm -f "$(DEPDIR)/NzbGenerator.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NzbGenerator.cpp' object='NzbGenerator.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbGenerator.o `test -f 'daemon/nserv/NzbGenerator.cpp' || echo '$(srcdir)/'`daemon/nserv/NzbGenerator.cpp
NzbGenerator.obj: daemon/nserv/NzbGenerator.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbGenerator.obj -MD -MP -MF "$(DEPDIR)/NzbGenerator.Tpo" -c -o NzbGenerator.obj `if test -f 'daemon/nserv/NzbGenerator.cpp'; then $(CYGPATH_W) 'daemon/nserv/NzbGenerator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NzbGenerator.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbGenerator.Tpo" "$(DEPDIR)/NzbGenerator.Po"; else rm -f "$(DEPDIR)/NzbGenerator.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NzbGenerator.cpp' object='NzbGenerator.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbGenerator.obj `if test -f 'daemon/nserv/NzbGenerator.cpp'; then $(CYGPATH_W) 'daemon/nserv/NzbGenerator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NzbGenerator.cpp'; fi`
YEncoder.o: daemon/nserv/YEncoder.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT YEncoder.o -MD -MP -MF "$(DEPDIR)/YEncoder.Tpo" -c -o YEncoder.o `test -f 'daemon/nserv/YEncoder.cpp' || echo '$(srcdir)/'`daemon/nserv/YEncoder.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/YEncoder.Tpo" "$(DEPDIR)/YEncoder.Po"; else rm -f "$(DEPDIR)/YEncoder.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/YEncoder.cpp' object='YEncoder.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o YEncoder.o `test -f 'daemon/nserv/YEncoder.cpp' || echo '$(srcdir)/'`daemon/nserv/YEncoder.cpp
YEncoder.obj: daemon/nserv/YEncoder.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT YEncoder.obj -MD -MP -MF "$(DEPDIR)/YEncoder.Tpo" -c -o YEncoder.obj `if test -f 'daemon/nserv/YEncoder.cpp'; then $(CYGPATH_W) 'daemon/nserv/YEncoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/YEncoder.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/YEncoder.Tpo" "$(DEPDIR)/YEncoder.Po"; else rm -f "$(DEPDIR)/YEncoder.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/YEncoder.cpp' object='YEncoder.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o YEncoder.obj `if test -f 'daemon/nserv/YEncoder.cpp'; then $(CYGPATH_W) 'daemon/nserv/YEncoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/YEncoder.cpp'; fi`
commandline.o: lib/par2/commandline.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT commandline.o -MD -MP -MF "$(DEPDIR)/commandline.Tpo" -c -o commandline.o `test -f 'lib/par2/commandline.cpp' || echo '$(srcdir)/'`lib/par2/commandline.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/commandline.Tpo" "$(DEPDIR)/commandline.Po"; else rm -f "$(DEPDIR)/commandline.Tpo"; exit 1; fi
@@ -1938,20 +2171,6 @@ md5.obj: lib/par2/md5.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o md5.obj `if test -f 'lib/par2/md5.cpp'; then $(CYGPATH_W) 'lib/par2/md5.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/md5.cpp'; fi`
par2creatorsourcefile.o: lib/par2/par2creatorsourcefile.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.o -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp
par2creatorsourcefile.obj: lib/par2/par2creatorsourcefile.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.obj -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi`
par2fileformat.o: lib/par2/par2fileformat.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2fileformat.o -MD -MP -MF "$(DEPDIR)/par2fileformat.Tpo" -c -o par2fileformat.o `test -f 'lib/par2/par2fileformat.cpp' || echo '$(srcdir)/'`lib/par2/par2fileformat.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2fileformat.Tpo" "$(DEPDIR)/par2fileformat.Po"; else rm -f "$(DEPDIR)/par2fileformat.Tpo"; exit 1; fi
@@ -2134,34 +2353,6 @@ FeedFilterTest.obj: tests/feed/FeedFilterTest.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilterTest.obj `if test -f 'tests/feed/FeedFilterTest.cpp'; then $(CYGPATH_W) 'tests/feed/FeedFilterTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/feed/FeedFilterTest.cpp'; fi`
ParCheckerTest.o: tests/postprocess/ParCheckerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.o -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp
ParCheckerTest.obj: tests/postprocess/ParCheckerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.obj -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi`
ParRenamerTest.o: tests/postprocess/ParRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.o -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp
ParRenamerTest.obj: tests/postprocess/ParRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.obj -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`
DupeMatcherTest.o: tests/postprocess/DupeMatcherTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcherTest.o -MD -MP -MF "$(DEPDIR)/DupeMatcherTest.Tpo" -c -o DupeMatcherTest.o `test -f 'tests/postprocess/DupeMatcherTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DupeMatcherTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcherTest.Tpo" "$(DEPDIR)/DupeMatcherTest.Po"; else rm -f "$(DEPDIR)/DupeMatcherTest.Tpo"; exit 1; fi
@@ -2176,6 +2367,48 @@ DupeMatcherTest.obj: tests/postprocess/DupeMatcherTest.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcherTest.obj `if test -f 'tests/postprocess/DupeMatcherTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DupeMatcherTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DupeMatcherTest.cpp'; fi`
RarRenamerTest.o: tests/postprocess/RarRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamerTest.o -MD -MP -MF "$(DEPDIR)/RarRenamerTest.Tpo" -c -o RarRenamerTest.o `test -f 'tests/postprocess/RarRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarRenamerTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamerTest.Tpo" "$(DEPDIR)/RarRenamerTest.Po"; else rm -f "$(DEPDIR)/RarRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarRenamerTest.cpp' object='RarRenamerTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamerTest.o `test -f 'tests/postprocess/RarRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarRenamerTest.cpp
RarRenamerTest.obj: tests/postprocess/RarRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamerTest.obj -MD -MP -MF "$(DEPDIR)/RarRenamerTest.Tpo" -c -o RarRenamerTest.obj `if test -f 'tests/postprocess/RarRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarRenamerTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamerTest.Tpo" "$(DEPDIR)/RarRenamerTest.Po"; else rm -f "$(DEPDIR)/RarRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarRenamerTest.cpp' object='RarRenamerTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamerTest.obj `if test -f 'tests/postprocess/RarRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarRenamerTest.cpp'; fi`
RarReaderTest.o: tests/postprocess/RarReaderTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReaderTest.o -MD -MP -MF "$(DEPDIR)/RarReaderTest.Tpo" -c -o RarReaderTest.o `test -f 'tests/postprocess/RarReaderTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarReaderTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReaderTest.Tpo" "$(DEPDIR)/RarReaderTest.Po"; else rm -f "$(DEPDIR)/RarReaderTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarReaderTest.cpp' object='RarReaderTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReaderTest.o `test -f 'tests/postprocess/RarReaderTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarReaderTest.cpp
RarReaderTest.obj: tests/postprocess/RarReaderTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReaderTest.obj -MD -MP -MF "$(DEPDIR)/RarReaderTest.Tpo" -c -o RarReaderTest.obj `if test -f 'tests/postprocess/RarReaderTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarReaderTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarReaderTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReaderTest.Tpo" "$(DEPDIR)/RarReaderTest.Po"; else rm -f "$(DEPDIR)/RarReaderTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarReaderTest.cpp' object='RarReaderTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReaderTest.obj `if test -f 'tests/postprocess/RarReaderTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarReaderTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarReaderTest.cpp'; fi`
DirectUnpackTest.o: tests/postprocess/DirectUnpackTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectUnpackTest.o -MD -MP -MF "$(DEPDIR)/DirectUnpackTest.Tpo" -c -o DirectUnpackTest.o `test -f 'tests/postprocess/DirectUnpackTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DirectUnpackTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectUnpackTest.Tpo" "$(DEPDIR)/DirectUnpackTest.Po"; else rm -f "$(DEPDIR)/DirectUnpackTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DirectUnpackTest.cpp' object='DirectUnpackTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectUnpackTest.o `test -f 'tests/postprocess/DirectUnpackTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DirectUnpackTest.cpp
DirectUnpackTest.obj: tests/postprocess/DirectUnpackTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DirectUnpackTest.obj -MD -MP -MF "$(DEPDIR)/DirectUnpackTest.Tpo" -c -o DirectUnpackTest.obj `if test -f 'tests/postprocess/DirectUnpackTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DirectUnpackTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DirectUnpackTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DirectUnpackTest.Tpo" "$(DEPDIR)/DirectUnpackTest.Po"; else rm -f "$(DEPDIR)/DirectUnpackTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DirectUnpackTest.cpp' object='DirectUnpackTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DirectUnpackTest.obj `if test -f 'tests/postprocess/DirectUnpackTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DirectUnpackTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DirectUnpackTest.cpp'; fi`
NzbFileTest.o: tests/queue/NzbFileTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbFileTest.o -MD -MP -MF "$(DEPDIR)/NzbFileTest.Tpo" -c -o NzbFileTest.o `test -f 'tests/queue/NzbFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NzbFileTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbFileTest.Tpo" "$(DEPDIR)/NzbFileTest.Po"; else rm -f "$(DEPDIR)/NzbFileTest.Tpo"; exit 1; fi
@@ -2245,6 +2478,34 @@ UtilTest.obj: tests/util/UtilTest.cpp
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`
ParCheckerTest.o: tests/postprocess/ParCheckerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.o -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp
ParCheckerTest.obj: tests/postprocess/ParCheckerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.obj -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi`
ParRenamerTest.o: tests/postprocess/ParRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.o -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.o' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp
ParRenamerTest.obj: tests/postprocess/ParRenamerTest.cpp
@am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.obj -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`
uninstall-info-am:
install-dist_docDATA: $(dist_doc_DATA)
@$(NORMAL_INSTALL)
@@ -2351,7 +2612,7 @@ distclean-tags:
distdir: $(DISTFILES)
$(am__remove_distdir)
mkdir $(distdir)
$(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/linux $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/posix $(distdir)/scripts $(distdir)/tests/testdata/dupematcher1 $(distdir)/tests/testdata/dupematcher2 $(distdir)/tests/testdata/nzbfile $(distdir)/tests/testdata/parchecker $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup
$(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/linux $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/posix $(distdir)/scripts $(distdir)/tests/testdata/dupematcher1 $(distdir)/tests/testdata/dupematcher2 $(distdir)/tests/testdata/nzbfile $(distdir)/tests/testdata/parchecker $(distdir)/tests/testdata/rarrenamer $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
list='$(DISTFILES)'; for file in $$list; do \
@@ -2629,21 +2890,21 @@ uninstall-conf:
# 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)" ; \
B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p'`; \
M=`git status --porcelain` ; \
if test "$$M" != "" ; then \
M="M" ; \
fi ; \
if test "$$B" = "master" ; then \
V="$$M" ; \
elif test "$$B" = "develop" ; then \
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
V=`git rev-list HEAD | wc -l | xargs` ; \
V="$${V}$$M" ; \
else \
V="$(shell git rev-list HEAD | wc -l | xargs)" ; \
V=`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)"; \
H=`test -f ./code_revision.cpp && head -n 1 code_revision.cpp`; \
if test "/* $$V */" != "$$H" ; then \
( \
echo "/* $$V */" ;\

View File

@@ -14,5 +14,5 @@ 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;
- [Documentation](http://nzbget.net/documentation) - installation manuals, HOW-TOs, API;
- [Forum](http://forum.nzbget.net) - get support, share your ideas, scripts, add-ons.

View File

@@ -92,7 +92,10 @@
/* Define to 1 if you have the <ncurses/ncurses.h> header file. */
#undef HAVE_NCURSES_NCURSES_H
/* Define to 1 to use OpenSSL library for TLS/SSL-support. */
/* Define to 1 to use Nettle library for decryption. */
#undef HAVE_NETTLE
/* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */
#undef HAVE_OPENSSL
/* Define to 1 if you have the <regex.h> header file. */
@@ -131,6 +134,9 @@
/* Define to 1 if variadic macros are supported */
#undef HAVE_VARIADIC_MACROS
/* Define to 1 if OpenSSL supports function "X509_check_host". */
#undef HAVE_X509_CHECK_HOST
/* Define to 1 to exclude debug-code */
#undef NDEBUG

544
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.61 for nzbget 17.1.
# Generated by GNU Autoconf 2.61 for nzbget 19.0.
#
# Report bugs to <hugbug@users.sourceforge.net>.
#
@@ -574,8 +574,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
# Identity of this package.
PACKAGE_NAME='nzbget'
PACKAGE_TARNAME='nzbget'
PACKAGE_VERSION='17.1'
PACKAGE_STRING='nzbget 17.1'
PACKAGE_VERSION='19.0'
PACKAGE_STRING='nzbget 19.0'
PACKAGE_BUGREPORT='hugbug@users.sourceforge.net'
ac_unique_file="daemon/main/nzbget.cpp"
@@ -721,6 +721,8 @@ openssl_CFLAGS
openssl_LIBS
gnutls_CFLAGS
gnutls_LIBS
nettle_CFLAGS
nettle_LIBS
zlib_CFLAGS
zlib_LIBS
WITH_TESTS_TRUE
@@ -747,6 +749,8 @@ openssl_CFLAGS
openssl_LIBS
gnutls_CFLAGS
gnutls_LIBS
nettle_CFLAGS
nettle_LIBS
zlib_CFLAGS
zlib_LIBS'
@@ -1251,7 +1255,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures nzbget 17.1 to adapt to many kinds of systems.
\`configure' configures nzbget 19.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1322,7 +1326,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of nzbget 17.1:";;
short | recursive ) echo "Configuration of nzbget 19.0:";;
esac
cat <<\_ACEOF
@@ -1369,6 +1373,10 @@ Optional Packages:
GnuTLS include directory
--with-libgnutls-libraries=DIR
GnuTLS library directory
--with-libnettle-includes=DIR
Nettle include directory
--with-libnettle-libraries=DIR
Nettle library directory
--with-zlib-includes=DIR
zlib include directory
--with-zlib-libraries=DIR
@@ -1399,6 +1407,9 @@ Some influential environment variables:
gnutls_CFLAGS
C compiler flags for gnutls, overriding pkg-config
gnutls_LIBS linker flags for gnutls, overriding pkg-config
nettle_CFLAGS
C compiler flags for nettle, overriding pkg-config
nettle_LIBS linker flags for nettle, overriding pkg-config
zlib_CFLAGS C compiler flags for zlib, overriding pkg-config
zlib_LIBS linker flags for zlib, overriding pkg-config
@@ -1466,7 +1477,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
nzbget configure 17.1
nzbget configure 19.0
generated by GNU Autoconf 2.61
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1480,7 +1491,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by nzbget $as_me 17.1, which was
It was created by nzbget $as_me 19.0, which was
generated by GNU Autoconf 2.61. Invocation command line was
$ $0 $@
@@ -2276,7 +2287,7 @@ fi
# Define the identity of the package.
PACKAGE='nzbget'
VERSION='17.1'
VERSION='19.0'
cat >>confdefs.h <<_ACEOF
@@ -9797,9 +9808,9 @@ echo "$as_me: error: Couldn't find OpenSSL headers (ssl.h)" >&2;}
{ (exit 1); exit 1; }; }
fi
if test "$FOUND" = "yes"; then
{ echo "$as_me:$LINENO: checking for library containing CRYPTO_set_locking_callback" >&5
echo $ECHO_N "checking for library containing CRYPTO_set_locking_callback... $ECHO_C" >&6; }
if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
{ echo "$as_me:$LINENO: checking for library containing ASN1_OBJECT_free" >&5
echo $ECHO_N "checking for library containing ASN1_OBJECT_free... $ECHO_C" >&6; }
if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
@@ -9816,11 +9827,11 @@ cat >>conftest.$ac_ext <<_ACEOF
#ifdef __cplusplus
extern "C"
#endif
char CRYPTO_set_locking_callback ();
char ASN1_OBJECT_free ();
int
main ()
{
return CRYPTO_set_locking_callback ();
return ASN1_OBJECT_free ();
;
return 0;
}
@@ -9850,7 +9861,7 @@ eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
test ! -s conftest.err
} && test -s conftest$ac_exeext &&
$as_test_x conftest$ac_exeext; then
ac_cv_search_CRYPTO_set_locking_callback=$ac_res
ac_cv_search_ASN1_OBJECT_free=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
@@ -9860,26 +9871,26 @@ fi
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext
if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
break
fi
done
if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
:
else
ac_cv_search_CRYPTO_set_locking_callback=no
ac_cv_search_ASN1_OBJECT_free=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_CRYPTO_set_locking_callback" >&5
echo "${ECHO_T}$ac_cv_search_CRYPTO_set_locking_callback" >&6; }
ac_res=$ac_cv_search_CRYPTO_set_locking_callback
{ echo "$as_me:$LINENO: result: $ac_cv_search_ASN1_OBJECT_free" >&5
echo "${ECHO_T}$ac_cv_search_ASN1_OBJECT_free" >&6; }
ac_res=$ac_cv_search_ASN1_OBJECT_free
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
{ echo "$as_me:$LINENO: checking for library containing SSL_library_init" >&5
echo $ECHO_N "checking for library containing SSL_library_init... $ECHO_C" >&6; }
if test "${ac_cv_search_SSL_library_init+set}" = set; then
{ echo "$as_me:$LINENO: checking for library containing SSL_CTX_new" >&5
echo $ECHO_N "checking for library containing SSL_CTX_new... $ECHO_C" >&6; }
if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
@@ -9896,11 +9907,11 @@ cat >>conftest.$ac_ext <<_ACEOF
#ifdef __cplusplus
extern "C"
#endif
char SSL_library_init ();
char SSL_CTX_new ();
int
main ()
{
return SSL_library_init ();
return SSL_CTX_new ();
;
return 0;
}
@@ -9930,7 +9941,7 @@ eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
test ! -s conftest.err
} && test -s conftest$ac_exeext &&
$as_test_x conftest$ac_exeext; then
ac_cv_search_SSL_library_init=$ac_res
ac_cv_search_SSL_CTX_new=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
@@ -9940,21 +9951,21 @@ fi
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext
if test "${ac_cv_search_SSL_library_init+set}" = set; then
if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
break
fi
done
if test "${ac_cv_search_SSL_library_init+set}" = set; then
if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
:
else
ac_cv_search_SSL_library_init=no
ac_cv_search_SSL_CTX_new=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_SSL_library_init" >&5
echo "${ECHO_T}$ac_cv_search_SSL_library_init" >&6; }
ac_res=$ac_cv_search_SSL_library_init
{ echo "$as_me:$LINENO: result: $ac_cv_search_SSL_CTX_new" >&5
echo "${ECHO_T}$ac_cv_search_SSL_CTX_new" >&6; }
ac_res=$ac_cv_search_SSL_CTX_new
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
FOUND=yes
@@ -9978,6 +9989,93 @@ cat >>confdefs.h <<\_ACEOF
#define HAVE_OPENSSL 1
_ACEOF
{ echo "$as_me:$LINENO: checking for library containing X509_check_host" >&5
echo $ECHO_N "checking for library containing X509_check_host... $ECHO_C" >&6; }
if test "${ac_cv_search_X509_check_host+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char X509_check_host ();
int
main ()
{
return X509_check_host ();
;
return 0;
}
_ACEOF
for ac_lib in '' crypto; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext &&
$as_test_x conftest$ac_exeext; then
ac_cv_search_X509_check_host=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext
if test "${ac_cv_search_X509_check_host+set}" = set; then
break
fi
done
if test "${ac_cv_search_X509_check_host+set}" = set; then
:
else
ac_cv_search_X509_check_host=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_X509_check_host" >&5
echo "${ECHO_T}$ac_cv_search_X509_check_host" >&6; }
ac_res=$ac_cv_search_X509_check_host
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
cat >>confdefs.h <<\_ACEOF
#define HAVE_X509_CHECK_HOST 1
_ACEOF
fi
fi
fi
fi
@@ -10630,6 +10728,380 @@ echo "$as_me: error: Couldn't find GnuTLS library" >&2;}
cat >>confdefs.h <<\_ACEOF
#define HAVE_LIBGNUTLS 1
_ACEOF
fi
fi
if test "$TLSLIB" = "GnuTLS"; then
# Check whether --with-libnettle_includes was given.
if test "${with_libnettle_includes+set}" = set; then
withval=$with_libnettle_includes; CPPFLAGS="${CPPFLAGS} -I${withval}"
INCVAL="yes"
else
INCVAL="no"
fi
# Check whether --with-libnettle_libraries was given.
if test "${with_libnettle_libraries+set}" = set; then
withval=$with_libnettle_libraries; LDFLAGS="${LDFLAGS} -L${withval}"
LIBVAL="yes"
else
LIBVAL="no"
fi
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
pkg_failed=no
{ echo "$as_me:$LINENO: checking for nettle" >&5
echo $ECHO_N "checking for nettle... $ECHO_C" >&6; }
if test -n "$PKG_CONFIG"; then
if test -n "$nettle_CFLAGS"; then
pkg_cv_nettle_CFLAGS="$nettle_CFLAGS"
else
if test -n "$PKG_CONFIG" && \
{ (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"nettle\"") >&5
($PKG_CONFIG --exists --print-errors "nettle") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; then
pkg_cv_nettle_CFLAGS=`$PKG_CONFIG --cflags "nettle" 2>/dev/null`
else
pkg_failed=yes
fi
fi
else
pkg_failed=untried
fi
if test -n "$PKG_CONFIG"; then
if test -n "$nettle_LIBS"; then
pkg_cv_nettle_LIBS="$nettle_LIBS"
else
if test -n "$PKG_CONFIG" && \
{ (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"nettle\"") >&5
($PKG_CONFIG --exists --print-errors "nettle") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; then
pkg_cv_nettle_LIBS=`$PKG_CONFIG --libs "nettle" 2>/dev/null`
else
pkg_failed=yes
fi
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
nettle_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "nettle"`
else
nettle_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "nettle"`
fi
# Put the nasty error message in config.log where it belongs
echo "$nettle_PKG_ERRORS" >&5
{ { echo "$as_me:$LINENO: error: Package requirements (nettle) were not met:
$nettle_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables nettle_CFLAGS
and nettle_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
" >&5
echo "$as_me: error: Package requirements (nettle) were not met:
$nettle_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables nettle_CFLAGS
and nettle_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
" >&2;}
{ (exit 1); exit 1; }; }
elif test $pkg_failed = untried; then
{ { echo "$as_me:$LINENO: error: 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.
Alternatively, you may set the environment variables nettle_CFLAGS
and nettle_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see <http://pkg-config.freedesktop.org/>.
See \`config.log' for more details." >&5
echo "$as_me: error: 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.
Alternatively, you may set the environment variables nettle_CFLAGS
and nettle_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see <http://pkg-config.freedesktop.org/>.
See \`config.log' for more details." >&2;}
{ (exit 1); exit 1; }; }
else
nettle_CFLAGS=$pkg_cv_nettle_CFLAGS
nettle_LIBS=$pkg_cv_nettle_LIBS
{ echo "$as_me:$LINENO: result: yes" >&5
echo "${ECHO_T}yes" >&6; }
LIBS="${LIBS} $nettle_LIBS"
CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"
fi
fi
if test "${ac_cv_header_nettle_sha_h+set}" = set; then
{ echo "$as_me:$LINENO: checking for nettle/sha.h" >&5
echo $ECHO_N "checking for nettle/sha.h... $ECHO_C" >&6; }
if test "${ac_cv_header_nettle_sha_h+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
fi
{ echo "$as_me:$LINENO: result: $ac_cv_header_nettle_sha_h" >&5
echo "${ECHO_T}$ac_cv_header_nettle_sha_h" >&6; }
else
# Is the header compilable?
{ echo "$as_me:$LINENO: checking nettle/sha.h usability" >&5
echo $ECHO_N "checking nettle/sha.h usability... $ECHO_C" >&6; }
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
$ac_includes_default
#include <nettle/sha.h>
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_header_compiler=yes
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_header_compiler=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
echo "${ECHO_T}$ac_header_compiler" >&6; }
# Is the header present?
{ echo "$as_me:$LINENO: checking nettle/sha.h presence" >&5
echo $ECHO_N "checking nettle/sha.h presence... $ECHO_C" >&6; }
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#include <nettle/sha.h>
_ACEOF
if { (ac_try="$ac_cpp conftest.$ac_ext"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } >/dev/null && {
test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
test ! -s conftest.err
}; then
ac_header_preproc=yes
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_header_preproc=no
fi
rm -f conftest.err conftest.$ac_ext
{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
echo "${ECHO_T}$ac_header_preproc" >&6; }
# So? What about this header?
case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in
yes:no: )
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: accepted by the compiler, rejected by the preprocessor!" >&5
echo "$as_me: WARNING: nettle/sha.h: accepted by the compiler, rejected by the preprocessor!" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: proceeding with the compiler's result" >&5
echo "$as_me: WARNING: nettle/sha.h: proceeding with the compiler's result" >&2;}
ac_header_preproc=yes
;;
no:yes:* )
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: present but cannot be compiled" >&5
echo "$as_me: WARNING: nettle/sha.h: present but cannot be compiled" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: check for missing prerequisite headers?" >&5
echo "$as_me: WARNING: nettle/sha.h: check for missing prerequisite headers?" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: see the Autoconf documentation" >&5
echo "$as_me: WARNING: nettle/sha.h: see the Autoconf documentation" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: section \"Present But Cannot Be Compiled\"" >&5
echo "$as_me: WARNING: nettle/sha.h: section \"Present But Cannot Be Compiled\"" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: proceeding with the preprocessor's result" >&5
echo "$as_me: WARNING: nettle/sha.h: proceeding with the preprocessor's result" >&2;}
{ echo "$as_me:$LINENO: WARNING: nettle/sha.h: in the future, the compiler will take precedence" >&5
echo "$as_me: WARNING: nettle/sha.h: in the future, the compiler will take precedence" >&2;}
( cat <<\_ASBOX
## ------------------------------------------- ##
## Report this to hugbug@users.sourceforge.net ##
## ------------------------------------------- ##
_ASBOX
) | sed "s/^/$as_me: WARNING: /" >&2
;;
esac
{ echo "$as_me:$LINENO: checking for nettle/sha.h" >&5
echo $ECHO_N "checking for nettle/sha.h... $ECHO_C" >&6; }
if test "${ac_cv_header_nettle_sha_h+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_cv_header_nettle_sha_h=$ac_header_preproc
fi
{ echo "$as_me:$LINENO: result: $ac_cv_header_nettle_sha_h" >&5
echo "${ECHO_T}$ac_cv_header_nettle_sha_h" >&6; }
fi
if test $ac_cv_header_nettle_sha_h = yes; then
FOUND=yes
else
FOUND=no
fi
if test "$FOUND" = "no"; then
{ { echo "$as_me:$LINENO: error: Couldn't find Nettle headers (sha.h)" >&5
echo "$as_me: error: Couldn't find Nettle headers (sha.h)" >&2;}
{ (exit 1); exit 1; }; }
fi
{ echo "$as_me:$LINENO: checking for library containing nettle_pbkdf2_hmac_sha256" >&5
echo $ECHO_N "checking for library containing nettle_pbkdf2_hmac_sha256... $ECHO_C" >&6; }
if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char nettle_pbkdf2_hmac_sha256 ();
int
main ()
{
return nettle_pbkdf2_hmac_sha256 ();
;
return 0;
}
_ACEOF
for ac_lib in '' nettle; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext &&
$as_test_x conftest$ac_exeext; then
ac_cv_search_nettle_pbkdf2_hmac_sha256=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext
if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
break
fi
done
if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
:
else
ac_cv_search_nettle_pbkdf2_hmac_sha256=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_nettle_pbkdf2_hmac_sha256" >&5
echo "${ECHO_T}$ac_cv_search_nettle_pbkdf2_hmac_sha256" >&6; }
ac_res=$ac_cv_search_nettle_pbkdf2_hmac_sha256
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
FOUND=yes
else
FOUND=no
fi
if test "$FOUND" = "no"; then
{ { echo "$as_me:$LINENO: error: Couldn't find Nettle library, required when using GnuTLS" >&5
echo "$as_me: error: Couldn't find Nettle library, required when using GnuTLS" >&2;}
{ (exit 1); exit 1; }; }
fi
if test "$FOUND" = "yes"; then
cat >>confdefs.h <<\_ACEOF
#define HAVE_NETTLE 1
_ACEOF
fi
@@ -11825,7 +12297,7 @@ exec 6>&1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by nzbget $as_me 17.1, which was
This file was extended by nzbget $as_me 19.0, which was
generated by GNU Autoconf 2.61. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -11878,7 +12350,7 @@ Report bugs to <bug-autoconf@gnu.org>."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
nzbget config.status 17.1
nzbget config.status 19.0
configured by $0, generated by GNU Autoconf 2.61,
with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
@@ -12203,6 +12675,8 @@ openssl_CFLAGS!$openssl_CFLAGS$ac_delim
openssl_LIBS!$openssl_LIBS$ac_delim
gnutls_CFLAGS!$gnutls_CFLAGS$ac_delim
gnutls_LIBS!$gnutls_LIBS$ac_delim
nettle_CFLAGS!$nettle_CFLAGS$ac_delim
nettle_LIBS!$nettle_LIBS$ac_delim
zlib_CFLAGS!$zlib_CFLAGS$ac_delim
zlib_LIBS!$zlib_LIBS$ac_delim
WITH_TESTS_TRUE!$WITH_TESTS_TRUE$ac_delim
@@ -12211,7 +12685,7 @@ LIBOBJS!$LIBOBJS$ac_delim
LTLIBOBJS!$LTLIBOBJS$ac_delim
_ACEOF
if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 15; then
if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 17; then
break
elif $ac_last_try; then
{ { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5

View File

@@ -1,7 +1,7 @@
#
# This file is part of nzbget. See <http://nzbget.net>.
#
# Copyright (C) 2008-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
# Copyright (C) 2008-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(nzbget, 17.1, hugbug@users.sourceforge.net)
AC_INIT(nzbget, 19.0, hugbug@users.sourceforge.net)
AC_CONFIG_AUX_DIR(posix)
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign])
@@ -385,8 +385,8 @@ if test "$USETLS" = "yes"; then
AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)])
fi
if test "$FOUND" = "yes"; then
AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto],
AC_SEARCH_LIBS([SSL_library_init], [ssl],
AC_SEARCH_LIBS([ASN1_OBJECT_free], [crypto],
AC_SEARCH_LIBS([SSL_CTX_new], [ssl],
FOUND=yes,
FOUND=no),
FOUND=no)
@@ -395,7 +395,9 @@ if test "$USETLS" = "yes"; then
fi
if test "$FOUND" = "yes"; then
TLSLIB="OpenSSL"
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.])
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support and decryption.])
AC_SEARCH_LIBS([X509_check_host], [crypto],
AC_DEFINE([HAVE_X509_CHECK_HOST],1,[Define to 1 if OpenSSL supports function "X509_check_host".]))
fi
fi
fi
@@ -457,6 +459,39 @@ if test "$USETLS" = "yes"; then
AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.])
fi
fi
if test "$TLSLIB" = "GnuTLS"; then
AC_ARG_WITH(libnettle_includes,
[AS_HELP_STRING([--with-libnettle-includes=DIR], [Nettle include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libnettle_libraries,
[AS_HELP_STRING([--with-libnettle-libraries=DIR], [Nettle library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([nettle], [nettle],
[LIBS="${LIBS} $nettle_LIBS"]
[CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"])
fi
AC_CHECK_HEADER(nettle/sha.h,
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle headers (sha.h)])
fi
AC_SEARCH_LIBS([nettle_pbkdf2_hmac_sha256], [nettle],
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle library, required when using GnuTLS])
fi
if test "$FOUND" = "yes"; then
AC_DEFINE([HAVE_NETTLE],1,[Define to 1 to use Nettle library for decryption.])
fi
fi
fi
if test "$TLSLIB" = ""; then

View File

@@ -117,7 +117,6 @@ void Connection::Final()
Connection::Connection(const char* host, int port, bool tls) :
m_host(host), m_port(port), m_tls(tls)
{
debug("Creating Connection");
@@ -214,7 +213,7 @@ bool Connection::Bind()
struct addrinfo addr_hints, *addr_list, *addr;
memset(&addr_hints, 0, sizeof(addr_hints));
addr_hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
addr_hints.ai_family = m_ipVersion == ipV4 ? AF_INET : m_ipVersion == ipV6 ? AF_INET6 : AF_UNSPEC;
addr_hints.ai_socktype = SOCK_STREAM,
addr_hints.ai_flags = AI_PASSIVE; // For wildcard IP address
@@ -374,7 +373,7 @@ char* Connection::ReadLine(char* buffer, int size, int* bytesReadOut)
bufAvail = recv(m_socket, m_readBuf, m_readBuf.Size() - 1, 0);
if (bufAvail < 0)
{
ReportError("Could not receive data on socket", nullptr, true);
ReportError("Could not receive data on socket from %s", m_host, true);
m_broken = true;
break;
}
@@ -446,7 +445,7 @@ std::unique_ptr<Connection> Connection::Accept()
SOCKET socket = accept(m_socket, nullptr, nullptr);
if (socket == INVALID_SOCKET && m_status != csCancelled)
{
ReportError("Could not accept connection", nullptr, true);
ReportError("Could not accept connection for %s", m_host, true);
}
if (socket == INVALID_SOCKET)
{
@@ -466,7 +465,7 @@ int Connection::TryRecv(char* buffer, int size)
if (received < 0)
{
ReportError("Could not receive data on socket", nullptr, true);
ReportError("Could not receive data on socket from %s", m_host, true);
}
return received;
@@ -498,7 +497,7 @@ bool Connection::Recv(char * buffer, int size)
// Did the recv succeed?
if (received <= 0)
{
ReportError("Could not receive data on socket", nullptr, true);
ReportError("Could not receive data on socket from %s", m_host, true);
return false;
}
bufPtr += received;
@@ -518,7 +517,7 @@ bool Connection::DoConnect()
struct addrinfo addr_hints, *addr_list, *addr;
memset(&addr_hints, 0, sizeof(addr_hints));
addr_hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
addr_hints.ai_family = m_ipVersion == ipV4 ? AF_INET : m_ipVersion == ipV6 ? AF_INET6 : AF_UNSPEC;
addr_hints.ai_socktype = SOCK_STREAM;
BString<100> portStr("%d", m_port);
@@ -813,7 +812,7 @@ void Connection::Cancel()
int r = shutdown(m_socket, SHUT_RDWR);
if (r == -1)
{
ReportError("Could not shutdown connection", nullptr, true);
ReportError("Could not shutdown connection for %s", m_host, true);
}
}
}

View File

@@ -44,6 +44,13 @@ public:
csCancelled
};
enum EIPVersion
{
ipAuto,
ipV4,
ipV6
};
Connection(const char* host, int port, bool tls);
Connection(SOCKET socket, bool tls);
virtual ~Connection();
@@ -65,6 +72,7 @@ public:
const char* GetCipher() { return m_cipher; }
void SetCipher(const char* cipher) { m_cipher = cipher; }
void SetTimeout(int timeout) { m_timeout = timeout; }
void SetIPVersion(EIPVersion ipVersion) { m_ipVersion = ipVersion; }
EStatus GetStatus() { return m_status; }
void SetSuppressErrors(bool suppressErrors);
bool GetSuppressErrors() { return m_suppressErrors; }
@@ -80,6 +88,7 @@ protected:
CString m_host;
int m_port;
bool m_tls;
EIPVersion m_ipVersion = ipAuto;
SOCKET m_socket = INVALID_SOCKET;
CString m_cipher;
CharBuffer m_readBuf;

View File

@@ -25,6 +25,8 @@
#include "TlsSocket.h"
#include "Thread.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
class TlsSocketFinalizer
{
@@ -36,6 +38,7 @@ public:
};
std::unique_ptr<TlsSocketFinalizer> m_tlsSocketFinalizer;
CString TlsSocket::m_certStore;
#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
@@ -89,6 +92,12 @@ static struct gcry_thread_cbs gcry_threads_Mutex =
#ifdef HAVE_OPENSSL
#ifndef CRYPTO_set_locking_callback
#define NEED_CRYPTO_LOCKING
#endif
#ifdef NEED_CRYPTO_LOCKING
/**
* Mutexes for OpenSSL
*/
@@ -108,17 +117,6 @@ static void openssl_locking(int mode, int n, const char* file, int line)
}
}
/*
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();
@@ -143,6 +141,7 @@ static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const
}
}
#endif /* NEED_CRYPTO_LOCKING */
#endif /* HAVE_OPENSSL */
@@ -172,20 +171,22 @@ void TlsSocket::Init()
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifdef NEED_CRYPTO_LOCKING
for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
{
g_OpenSSLMutexes.emplace_back(std::make_unique<Mutex>());
}
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 /* NEED_CRYPTO_LOCKING */
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* HAVE_OPENSSL */
@@ -204,11 +205,11 @@ TlsSocket::~TlsSocket()
Close();
}
void TlsSocket::ReportError(const char* errMsg)
void TlsSocket::ReportError(const char* errMsg, bool suppressable)
{
#ifdef HAVE_LIBGNUTLS
const char* errstr = gnutls_strerror(m_retCode);
if (m_suppressErrors)
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
@@ -219,16 +220,14 @@ void TlsSocket::ReportError(const char* errMsg)
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int errcode;
int errcode = ERR_get_error();
do
{
errcode = ERR_get_error();
char errstr[1024];
ERR_error_string_n(errcode, errstr, sizeof(errstr));
errstr[1024-1] = '\0';
if (m_suppressErrors)
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
@@ -240,6 +239,8 @@ void TlsSocket::ReportError(const char* errMsg)
{
PrintError(errMsg);
}
errcode = ERR_get_error();
} while (errcode);
#endif /* HAVE_OPENSSL */
}
@@ -256,7 +257,7 @@ bool TlsSocket::Start()
m_retCode = gnutls_certificate_allocate_credentials(&cred);
if (m_retCode != 0)
{
ReportError("Could not create TLS context");
ReportError("Could not create TLS context", false);
return false;
}
@@ -268,7 +269,7 @@ bool TlsSocket::Start()
m_certFile, m_keyFile, GNUTLS_X509_FMT_PEM);
if (m_retCode != 0)
{
ReportError("Could not load certificate or key file");
ReportError("Could not load certificate or key file", false);
Close();
return false;
}
@@ -278,7 +279,7 @@ bool TlsSocket::Start()
m_retCode = gnutls_init(&sess, m_isClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (m_retCode != 0)
{
ReportError("Could not create TLS session");
ReportError("Could not create TLS session", false);
Close();
return false;
}
@@ -287,12 +288,13 @@ bool TlsSocket::Start()
m_initialized = true;
const char* priority = !m_cipher.Empty() ? m_cipher.Str() : "NORMAL";
const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
(m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
if (m_retCode != 0)
{
ReportError("Could not select cipher for TLS");
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
@@ -302,7 +304,7 @@ bool TlsSocket::Start()
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");
ReportError("Could not set hostname for TLS");
Close();
return false;
}
@@ -312,7 +314,7 @@ bool TlsSocket::Start()
(gnutls_certificate_credentials_t*)m_context);
if (m_retCode != 0)
{
ReportError("Could not initialize TLS session");
ReportError("Could not initialize TLS session", false);
Close();
return false;
}
@@ -322,7 +324,13 @@ bool TlsSocket::Start()
m_retCode = gnutls_handshake((gnutls_session_t)m_session);
if (m_retCode != 0)
{
ReportError("TLS handshake failed");
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
@@ -336,7 +344,7 @@ bool TlsSocket::Start()
if (!m_context)
{
ReportError("Could not create TLS context");
ReportError("Could not create TLS context", false);
return false;
}
@@ -344,29 +352,65 @@ bool TlsSocket::Start()
{
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile) != 1)
{
ReportError("Could not load certificate file");
ReportError("Could not load certificate file", false);
Close();
return false;
}
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile, SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load key file");
ReportError("Could not load key file", false);
Close();
return false;
}
if (!SSL_CTX_set_options((SSL_CTX*)m_context, SSL_OP_NO_SSLv3))
{
ReportError("Could not select minimum protocol version for TLS", false);
Close();
return false;
}
// For ECC certificates
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!ecdh)
{
ReportError("Could not generate ecdh parameters for TLS", false);
Close();
return false;
}
if (!SSL_CTX_set_tmp_ecdh((SSL_CTX*)m_context, ecdh))
{
ReportError("Could not set ecdh parameters for TLS", false);
EC_KEY_free(ecdh);
Close();
return false;
}
EC_KEY_free(ecdh);
}
if (m_isClient && !m_certStore.Empty())
{
// Enable certificate validation
if (SSL_CTX_load_verify_locations((SSL_CTX*)m_context, m_certStore, nullptr) != 1)
{
ReportError("Could not set certificate store location", false);
Close();
return false;
}
SSL_CTX_set_verify((SSL_CTX*)m_context, SSL_VERIFY_PEER, nullptr);
}
m_session = SSL_new((SSL_CTX*)m_context);
if (!m_session)
{
ReportError("Could not create TLS session");
ReportError("Could not create TLS session", false);
Close();
return false;
}
if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
{
ReportError("Could not select cipher for TLS");
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
@@ -388,7 +432,23 @@ bool TlsSocket::Start()
int error_code = m_isClient ? SSL_connect((SSL*)m_session) : SSL_accept((SSL*)m_session);
if (error_code < 1)
{
ReportError("TLS handshake failed");
long verifyRes = SSL_get_verify_result((SSL*)m_session);
if (verifyRes != X509_V_OK)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification",
*m_host, X509_verify_cert_error_string(verifyRes)));
}
else
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
}
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
@@ -398,6 +458,124 @@ bool TlsSocket::Start()
#endif /* HAVE_OPENSSL */
}
bool TlsSocket::ValidateCert()
{
#ifdef HAVE_LIBGNUTLS
#if GNUTLS_VERSION_NUMBER >= 0x030104
#if GNUTLS_VERSION_NUMBER >= 0x030306
if (FileSystem::DirectoryExists(m_certStore))
{
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
else
#endif
{
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
unsigned int status = 0;
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host, &status) != 0 ||
gnutls_certificate_type_get((gnutls_session_t)m_session) != GNUTLS_CRT_X509)
{
ReportError("Could not verify TLS certificate");
return false;
}
if (status != 0)
{
if (status & GNUTLS_CERT_UNEXPECTED_OWNER)
{
// Extracting hostname from the certificate
unsigned int cert_list_size = 0;
const gnutls_datum_t* cert_list = gnutls_certificate_get_peers((gnutls_session_t)m_session, &cert_list_size);
if (cert_list_size > 0)
{
gnutls_x509_crt_t cert;
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
char dn[256];
size_t size = sizeof(dn);
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn, &size) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, dn));
gnutls_x509_crt_deinit(cert);
return false;
}
gnutls_x509_crt_deinit(cert);
}
}
gnutls_datum_t msgdata;
if (gnutls_certificate-verification_status_print(status, GNUTLS_CRT_X509, &msgdata, 0) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host, msgdata.data));
gnutls_free(&msgdata);
}
else
{
ReportError(BString<1024>("TLS certificate verification failed for %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
}
return false;
}
#endif
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
// verify a server certificate was presented during the negotiation
X509* cert = SSL_get_peer_certificate((SSL*)m_session);
if (!cert)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: no certificate provided by server."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
return false;
}
#ifdef HAVE_X509_CHECK_HOST
// hostname verification
if (!m_host.Empty() && X509_check_host(cert, m_host, m_host.Length(), 0, nullptr) != 1)
{
char* certHost = nullptr;
// Find the position of the CN field in the Subject field of the certificate
int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
if (common_name_loc >= 0)
{
// Extract the CN field
X509_NAME_ENTRY* common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
if (common_name_entry != nullptr)
{
// Convert the CN field to a C string
ASN1_STRING* common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 != nullptr)
{
certHost = (char*)ASN1_STRING_data(common_name_asn1);
}
}
}
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, certHost));
X509_free(cert);
return false;
}
#endif
X509_free(cert);
return true;
#endif /* HAVE_OPENSSL */
}
void TlsSocket::Close()
{
if (m_session)

View File

@@ -33,6 +33,7 @@ public:
m_certFile(certFile), m_keyFile(keyFile), m_cipher(cipher) {}
virtual ~TlsSocket();
static void Init();
static void InitOptions(const char* certStore) { m_certStore = certStore; }
bool Start();
void Close();
int Send(const char* buffer, int size);
@@ -41,7 +42,7 @@ public:
protected:
virtual void PrintError(const char* errMsg);
private:
bool m_isClient;
CString m_host;
@@ -53,12 +54,14 @@ private:
bool m_initialized = false;
bool m_connected = false;
int m_retCode;
static CString m_certStore;
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
void* m_context = nullptr;
void* m_session = nullptr;
void ReportError(const char* errMsg);
void ReportError(const char* errMsg, bool suppressable = true);
bool ValidateCert();
static void Final();
friend class TlsSocketFinalizer;

View File

@@ -54,7 +54,7 @@ void WebDownloader::Run()
SetStatus(adRunning);
int remainedDownloadRetries = g_Options->GetRetries() > 0 ? g_Options->GetRetries() : 1;
int remainedDownloadRetries = g_Options->GetUrlRetries() > 0 ? g_Options->GetUrlRetries() : 1;
int remainedConnectRetries = remainedDownloadRetries > 10 ? remainedDownloadRetries : 10;
if (!m_retry)
{
@@ -74,9 +74,9 @@ void WebDownloader::Run()
((Status == adConnectError) && (remainedConnectRetries > 1)))
&& !IsStopped() && !(!m_force && g_Options->GetPauseDownload()))
{
detail("Waiting %i sec to retry", g_Options->GetRetryInterval());
detail("Waiting %i sec to retry", g_Options->GetUrlInterval());
int msec = 0;
while (!IsStopped() && (msec < g_Options->GetRetryInterval() * 1000) &&
while (!IsStopped() && (msec < g_Options->GetUrlInterval() * 1000) &&
!(!m_force && g_Options->GetPauseDownload()))
{
usleep(100 * 1000);

View File

@@ -0,0 +1,131 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "CommandScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
static const int COMMANDPROCESS_SUCCESS = 93;
static const int COMMANDPROCESS_ERROR = 94;
bool CommandScriptController::StartScript(const char* scriptName, const char* command,
std::unique_ptr<Options::OptEntries> modifiedOptions)
{
CommandScriptController* scriptController = new CommandScriptController();
scriptController->m_script = scriptName;
scriptController->m_command = command;
scriptController->m_logId = g_CommandScriptLog->Reset();
scriptController->m_modifiedOptions = std::move(modifiedOptions);
scriptController->SetAutoDestroy(true);
scriptController->Start();
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
return true;
}
}
return false;
}
void CommandScriptController::Run()
{
ExecuteScriptList(m_script);
}
void CommandScriptController::ExecuteScript(ScriptConfig::Script* script)
{
PrintMessage(Message::mkInfo, "Executing script %s with command %s", script->GetName(), *m_command);
SetArgs({script->GetLocation()});
BString<1024> infoName("script %s with command %s", script->GetName(), *m_command);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
int exitCode = Execute();
infoName[0] = 'S'; // uppercase
SetLogPrefix(nullptr);
switch (exitCode)
{
case COMMANDPROCESS_SUCCESS:
PrintMessage(Message::mkInfo, "%s successful", *infoName);
break;
case COMMANDPROCESS_ERROR:
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
PrintMessage(Message::mkError, "%s failed", *infoName);
break;
default:
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", *infoName);
break;
}
}
void CommandScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBCP_COMMAND", m_command);
PrepareEnvScript(nullptr, scriptName);
}
const char* CommandScriptController::GetOptValue(const char* name, const char* value)
{
Options::OptEntry* entry = m_modifiedOptions->FindOption(name);
return entry ? entry->GetValue() : value;
}
void CommandScriptController::AddMessage(Message::EKind kind, const char * text)
{
NzbScriptController::AddMessage(kind, text);
g_CommandScriptLog->AddMessage(m_logId, kind, text);
}
int CommandScriptLog::Reset()
{
Guard guard(m_logMutex);
m_messages.clear();
return ++m_idScriptGen;
}
void CommandScriptLog::AddMessage(int scriptId, Message::EKind kind, const char * text)
{
Guard guard(m_logMutex);
// save only messages from the last started script
if (scriptId == m_idScriptGen)
{
m_messages.emplace_back(++m_idMessageGen, kind, Util::CurrentTime(), text);
}
}

View File

@@ -0,0 +1,63 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COMMANDSCRIPT_H
#define COMMANDSCRIPT_H
#include "NzbScript.h"
#include "Log.h"
class CommandScriptController : public Thread, public NzbScriptController
{
public:
virtual void Run();
static bool StartScript(const char* scriptName, const char* command, std::unique_ptr<Options::OptEntries> modifiedOptions);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
virtual const char* GetOptValue(const char* name, const char* value);
private:
CString m_script;
CString m_command;
int m_logId;
std::unique_ptr<Options::OptEntries> m_modifiedOptions;
void PrepareParams(const char* scriptName);
};
class CommandScriptLog
{
public:
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
int Reset();
void AddMessage(int scriptId, Message::EKind kind, const char* text);
private:
MessageList m_messages;
Mutex m_logMutex;
int m_idMessageGen;
int m_idScriptGen;
};
extern CommandScriptLog* g_CommandScriptLog;
#endif

View File

@@ -62,7 +62,7 @@ void FeedScriptController::ExecuteScript(ScriptConfig::Script* script)
if (exitCode != FEED_SUCCESS)
{
infoName[0] = 'F'; // uppercase
PrintMessage(Message::mkError, "%s failed", GetInfoName());
PrintMessage(Message::mkError, "%s failed", *infoName);
m_success = false;
}

View File

@@ -100,7 +100,7 @@ void PostScriptController::ExecuteScript(ScriptConfig::Script* script)
infoName[0] = 'P'; // uppercase
SetLogPrefix(nullptr);
ScriptStatus::EStatus status = AnalyseExitCode(exitCode);
ScriptStatus::EStatus status = AnalyseExitCode(exitCode, infoName);
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
@@ -176,7 +176,7 @@ void PostScriptController::PrepareParams(const char* scriptName)
PrepareEnvScript(m_postInfo->GetNzbInfo()->GetParameters(), scriptName);
}
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode)
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode, const char* upInfoName)
{
// The ScriptStatus is accumulated for all scripts:
// If any script has failed the status is "failure", etc.
@@ -184,28 +184,28 @@ ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode)
switch (exitCode)
{
case POSTPROCESS_SUCCESS:
PrintMessage(Message::mkInfo, "%s successful", GetInfoName());
PrintMessage(Message::mkInfo, "%s successful", upInfoName);
return ScriptStatus::srSuccess;
case POSTPROCESS_ERROR:
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
PrintMessage(Message::mkError, "%s failed", GetInfoName());
PrintMessage(Message::mkError, "%s failed", upInfoName);
return ScriptStatus::srFailure;
case POSTPROCESS_NONE:
PrintMessage(Message::mkInfo, "%s skipped", GetInfoName());
PrintMessage(Message::mkInfo, "%s skipped", upInfoName);
return ScriptStatus::srNone;
#ifndef DISABLE_PARCHECK
case POSTPROCESS_PARCHECK:
if (m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped)
{
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName());
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", upInfoName);
return ScriptStatus::srFailure;
}
else
{
PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName());
PrintMessage(Message::mkInfo, "%s requested par-check/repair", upInfoName);
m_postInfo->SetRequestParCheck(true);
m_postInfo->SetForceRepair(true);
return ScriptStatus::srSuccess;
@@ -214,7 +214,7 @@ ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode)
#endif
default:
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName());
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", upInfoName);
return ScriptStatus::srFailure;
}
}

View File

@@ -41,7 +41,7 @@ private:
ScriptConfig::Script* m_script;
void PrepareParams(const char* scriptName);
ScriptStatus::EStatus AnalyseExitCode(int exitCode);
ScriptStatus::EStatus AnalyseExitCode(int exitCode, const char* upInfoName);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ static const char* QUEUE_EVENT_NAMES[] = {
"URL_COMPLETED",
"NZB_MARKED",
"NZB_ADDED",
"NZB_NAMED",
"NZB_DOWNLOADED",
"NZB_DELETED" };
@@ -112,7 +113,7 @@ void QueueScriptController::Run()
{
nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, 0, nullptr);
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
}
}
@@ -336,21 +337,7 @@ bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo*
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
// check extension scripts assigned for that nzb
for (NzbParameter& parameter : nzbInfo->GetParameters())
{
const char* varname = parameter.GetName();
@@ -368,17 +355,17 @@ bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo*
}
}
// for URL-events the post-processing scripts are not assigned yet;
// instead we take the default post-processing scripts for the category (or global)
// for URL-events the extension scripts are not assigned yet;
// instead we take the default extension scripts for the category (or global)
if (event == qeUrlCompleted)
{
const char* postScript = g_Options->GetPostScript();
const char* postScript = g_Options->GetExtensions();
if (!Util::EmptyStr(nzbInfo->GetCategory()))
{
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetPostScript()))
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
postScript = categoryObj->GetPostScript();
postScript = categoryObj->GetExtensions();
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@ public:
qeUrlCompleted,
qeNzbMarked,
qeNzbAdded,
qeNzbNamed,
qeNzbDownloaded,
qeNzbDeleted // highest priority
};

View File

@@ -25,6 +25,22 @@
#include "Log.h"
#include "FileSystem.h"
class ScanScriptCheck : public NzbScriptController
{
protected:
virtual void ExecuteScript(ScriptConfig::Script* script) { has |= script->GetScanScript(); }
bool has = false;
friend class ScanScriptController;
};
bool ScanScriptController::HasScripts()
{
ScanScriptCheck check;
check.ExecuteScriptList(g_Options->GetExtensions());
return check.has;
}
void ScanScriptController::ExecuteScripts(const char* nzbFilename,
const char* url, const char* directory, CString* nzbName, CString* category,
int* priority, NzbParameterList* parameters, bool* addTop, bool* addPaused,
@@ -45,7 +61,18 @@ void ScanScriptController::ExecuteScripts(const char* nzbFilename,
scriptController.m_dupeMode = dupeMode;
scriptController.m_prefixLen = 0;
scriptController.ExecuteScriptList(g_Options->GetScanScript());
const char* extensions = g_Options->GetExtensions();
if (!Util::EmptyStr(*category))
{
Options::Category* categoryObj = g_Options->FindCategory(*category, false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
extensions = categoryObj->GetExtensions();
}
}
scriptController.ExecuteScriptList(extensions);
}
void ScanScriptController::ExecuteScript(ScriptConfig::Script* script)

View File

@@ -30,6 +30,7 @@ public:
const char* directory, CString* nzbName, CString* category, int* priority,
NzbParameterList* parameters, bool* addTop, bool* addPaused,
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode);
static bool HasScripts();
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);

View File

@@ -69,11 +69,17 @@ void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* script)
return;
}
PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", script->GetName(), m_taskId);
BString<1024> taskName(" for Task%i", m_taskId);
if (m_taskId == 0)
{
taskName = "";
}
PrintMessage(Message::mkInfo, "Executing scheduler-script %s%s", script->GetName(), *taskName);
SetArgs({script->GetLocation()});
BString<1024> infoName("scheduler-script %s for Task%i", script->GetName(), m_taskId);
BString<1024> infoName("scheduler-script %s%s", script->GetName(), *taskName);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());

View File

@@ -33,25 +33,14 @@ 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;
}
static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
static const char* DEFINITION_SIGNATURE = "###";
void ScriptConfig::InitOptions()
{
InitScripts();
InitConfigTemplates();
CreateTasks();
}
bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
@@ -81,7 +70,7 @@ bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
CString optname;
CString optvalue;
if (g_Options->SplitOptionString(buf, optname, optvalue))
if (Options::SplitOptionString(buf, optname, optvalue))
{
optEntries->emplace_back(optname, optvalue);
}
@@ -89,6 +78,8 @@ bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
infile.Close();
Options::ConvertOldOptions(optEntries);
return true;
}
@@ -180,7 +171,7 @@ bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
LoadScripts(&scriptList);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
for (Script& script : scriptList)
{
@@ -194,6 +185,7 @@ bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
StringBuilder templ;
char buf[1024];
bool inConfig = false;
bool inHeader = false;
while (infile.ReadLine(buf, sizeof(buf) - 1))
{
@@ -210,12 +202,13 @@ bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
break;
}
inConfig = true;
inHeader = true;
continue;
}
bool skip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen);
inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
if (inConfig && !skip)
if (inConfig && !inHeader)
{
templ.Append(buf);
}
@@ -287,11 +280,6 @@ void ScriptConfig::LoadScripts(Scripts* 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())
{
@@ -307,54 +295,10 @@ void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool i
continue;
}
// check if the file contains pp-script-signature
DiskFile infile;
if (infile.Open(fullFilename, DiskFile::omRead))
Script script(scriptName, fullFilename);
if (LoadScriptFile(&script))
{
// 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;
}
}
}
scripts->push_back(std::move(script));
}
}
else if (!isSubDir)
@@ -365,6 +309,109 @@ void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool i
}
}
bool ScriptConfig::LoadScriptFile(Script* script)
{
DiskFile infile;
if (!infile.Open(script->GetLocation(), DiskFile::omRead))
{
return false;
}
CharBuffer buffer(1024 * 10 + 1);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
// check if the file contains pp-script-signature
// read first 10KB of the file and look for signature
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
infile.Close();
buffer[readBytes] = '\0';
bool postScript = false;
bool scanScript = false;
bool queueScript = false;
bool schedulerScript = false;
bool feedScript = false;
char* queueEvents = nullptr;
char* taskTime = nullptr;
bool inConfig = false;
bool afterConfig = false;
// Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
// - in script definition body (between opening and closing script signatures);
// - immediately before script definition (before opening script signature);
// - immediately after script definition (after closing script signature).
// The last two pissibilities are provided to increase compatibility of scripts with older
// nzbget versions which do not expect the extra declarations in the script defintion body.
Tokenizer tok(buffer, "\n\r", true);
while (char* line = tok.Next())
{
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
{
queueEvents = line + queueEventsSignatureLen;
}
else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
{
taskTime = line + taskTimeSignatureLen;
}
bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
if (!header && !inConfig)
{
queueEvents = nullptr;
taskTime = nullptr;
}
if (!header && afterConfig)
{
break;
}
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
{
if (!inConfig)
{
inConfig = true;
postScript = strstr(line, POST_SCRIPT_SIGNATURE);
scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
}
else
{
afterConfig = true;
}
}
}
if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
{
return false;
}
// trim decorations
char* p;
while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
if (queueEvents) queueEvents = Util::Trim(queueEvents);
while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
if (taskTime) taskTime = Util::Trim(taskTime);
script->SetPostScript(postScript);
script->SetScanScript(scanScript);
script->SetQueueScript(queueScript);
script->SetSchedulerScript(schedulerScript);
script->SetFeedScript(feedScript);
script->SetQueueEvents(queueEvents);
script->SetTaskTime(taskTime);
return true;
}
BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
{
@@ -431,3 +478,23 @@ void ScriptConfig::BuildScriptDisplayNames(Scripts* scripts)
script.SetDisplayName(displayName);
}
}
void ScriptConfig::CreateTasks()
{
for (Script& script : m_scripts)
{
if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
{
Tokenizer tok(g_Options->GetExtensions(), ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
nullptr, Options::scScript, script.GetName());
break;
}
}
}
}
}

View File

@@ -31,7 +31,8 @@ public:
class Script
{
public:
Script(const char* name, const char* location);
Script(const char* name, const char* location) :
m_name(name), m_location(location), m_displayName(name) {};
Script(Script&&) = default;
const char* GetName() { return m_name; }
const char* GetLocation() { return m_location; }
@@ -49,17 +50,20 @@ public:
void SetFeedScript(bool feedScript) { m_feedScript = feedScript; }
void SetQueueEvents(const char* queueEvents) { m_queueEvents = queueEvents; }
const char* GetQueueEvents() { return m_queueEvents; }
void SetTaskTime(const char* taskTime) { m_taskTime = taskTime; }
const char* GetTaskTime() { return m_taskTime; }
private:
CString m_name;
CString m_location;
CString m_displayName;
bool m_postScript;
bool m_scanScript;
bool m_queueScript;
bool m_schedulerScript;
bool m_feedScript;
bool m_postScript = false;
bool m_scanScript = false;
bool m_queueScript = false;
bool m_schedulerScript = false;
bool m_feedScript = false;
CString m_queueEvents;
CString m_taskTime;
};
typedef std::list<Script> Scripts;
@@ -75,8 +79,6 @@ public:
private:
Script m_script;
CString m_template;
friend class Options;
};
typedef std::deque<ConfigTemplate> ConfigTemplates;
@@ -94,9 +96,11 @@ private:
void InitScripts();
void InitConfigTemplates();
void CreateTasks();
void LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
void BuildScriptDisplayNames(Scripts* scripts);
void LoadScripts(Scripts* scripts);
bool LoadScriptFile(Script* script);
BString<1024>BuildScriptName(const char* directory, const char* filename, bool isSubDir);
bool ScriptExists(Scripts* scripts, const char* scriptName);
};

View File

@@ -239,14 +239,7 @@ void FeedCoordinator::StartFeedDownload(FeedInfo* feedInfo, bool force)
feedDownloader->Attach(this);
feedDownloader->SetFeedInfo(feedInfo);
feedDownloader->SetUrl(feedInfo->GetUrl());
if (strlen(feedInfo->GetName()) > 0)
{
feedDownloader->SetInfoName(feedInfo->GetName());
}
else
{
feedDownloader->SetInfoName(NzbInfo::MakeNiceUrlName(feedInfo->GetUrl(), ""));
}
feedDownloader->SetInfoName(feedInfo->GetName());
feedDownloader->SetForce(force || g_Options->GetUrlForce());
BString<1024> outFilename;
@@ -304,7 +297,7 @@ void FeedCoordinator::FeedCompleted(FeedDownloader* feedDownloader)
{
bool scriptSuccess = true;
FeedScriptController::ExecuteScripts(
!Util::EmptyStr(feedInfo->GetFeedScript()) ? feedInfo->GetFeedScript(): g_Options->GetFeedScript(),
!Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
feedInfo->GetOutputFilename(), feedInfo->GetId(), &scriptSuccess);
if (!scriptSuccess)
{
@@ -312,15 +305,13 @@ void FeedCoordinator::FeedCompleted(FeedDownloader* feedDownloader)
return;
}
std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename());
bool parsed = feedFile->Parse();
FileSystem::DeleteFile(feedInfo->GetOutputFilename());
std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo);
std::vector<std::unique_ptr<NzbInfo>> addedNzbs;
{
Guard guard(m_downloadsMutex);
if (parsed)
if (feedFile)
{
std::unique_ptr<FeedItemList> feedItems = feedFile->DetachFeedItems();
addedNzbs = ProcessFeed(feedInfo, feedItems.get());
@@ -469,7 +460,7 @@ std::shared_ptr<FeedItemList> FeedCoordinator::ViewFeed(int id)
return PreviewFeed(feedInfo->GetId(), feedInfo->GetName(), feedInfo->GetUrl(), feedInfo->GetFilter(),
feedInfo->GetBacklog(), feedInfo->GetPauseNzb(), feedInfo->GetCategory(),
feedInfo->GetPriority(), feedInfo->GetInterval(), feedInfo->GetFeedScript(), 0, nullptr);
feedInfo->GetPriority(), feedInfo->GetInterval(), feedInfo->GetExtensions(), 0, nullptr);
}
std::shared_ptr<FeedItemList> FeedCoordinator::PreviewFeed(int id,
@@ -535,14 +526,11 @@ std::shared_ptr<FeedItemList> FeedCoordinator::PreviewFeed(int id,
}
FeedScriptController::ExecuteScripts(
!Util::EmptyStr(feedInfo->GetFeedScript()) ? feedInfo->GetFeedScript(): g_Options->GetFeedScript(),
!Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
feedInfo->GetOutputFilename(), feedInfo->GetId(), nullptr);
std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename());
bool parsed = feedFile->Parse();
FileSystem::DeleteFile(feedInfo->GetOutputFilename());
if (!parsed)
std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo.get());
if (!feedFile)
{
return nullptr;
}
@@ -588,6 +576,21 @@ void FeedCoordinator::FetchFeed(int id)
}
}
std::unique_ptr<FeedFile> FeedCoordinator::parseFeed(FeedInfo* feedInfo)
{
std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename(), feedInfo->GetName());
if (feedFile->Parse())
{
FileSystem::DeleteFile(feedInfo->GetOutputFilename());
}
else
{
error("Feed file %s kept for troubleshooting (will be deleted on next successful feed fetch)", feedInfo->GetOutputFilename());
feedFile.reset();
}
return std::move(feedFile);
}
void FeedCoordinator::DownloadQueueUpdate(Subject* caller, void* aspect)
{
debug("Notification from URL-Coordinator received");

View File

@@ -26,6 +26,7 @@
#include "Thread.h"
#include "WebDownloader.h"
#include "DownloadInfo.h"
#include "FeedFile.h"
#include "FeedInfo.h"
#include "Observer.h"
#include "Util.h"
@@ -119,6 +120,7 @@ private:
void CleanupHistory();
void CleanupCache();
void CheckSaveFeeds();
std::unique_ptr<FeedFile> parseFeed(FeedInfo* feedInfo);
};
extern FeedCoordinator* g_FeedCoordinator;

View File

@@ -25,8 +25,8 @@
#include "Options.h"
#include "Util.h"
FeedFile::FeedFile(const char* fileName) :
m_fileName(fileName)
FeedFile::FeedFile(const char* fileName, const char* infoName) :
m_fileName(fileName), m_infoName(infoName)
{
debug("Creating FeedFile");
@@ -113,7 +113,7 @@ bool FeedFile::Parse()
{
_bstr_t r(doc->GetparseError()->reason);
const char* errMsg = r;
error("Error parsing rss feed: %s", errMsg);
error("Error parsing rss feed %s: %s", *m_infoName, errMsg);
return false;
}
@@ -248,7 +248,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
//<newznab:attr name="size" value="5423523453534" />
if (feedItemInfo.GetSize() == 0)
{
tag = node->selectSingleNode("newznab:attr[@name='size']");
tag = node->selectSingleNode("newznab:attr[@name='size'] | nZEDb:attr[@name='size']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -262,7 +262,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
}
//<newznab:attr name="imdb" value="1588173"/>
tag = node->selectSingleNode("newznab:attr[@name='imdb']");
tag = node->selectSingleNode("newznab:attr[@name='imdb'] | nZEDb:attr[@name='imdb']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -275,7 +275,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
}
//<newznab:attr name="rageid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='rageid']");
tag = node->selectSingleNode("newznab:attr[@name='rageid'] | nZEDb:attr[@name='rageid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -288,7 +288,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
}
//<newznab:attr name="tdvdbid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvdbid']");
tag = node->selectSingleNode("newznab:attr[@name='tvdbid'] | nZEDb:attr[@name='tvdbid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -301,7 +301,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
}
//<newznab:attr name="tvmazeid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvmazeid']");
tag = node->selectSingleNode("newznab:attr[@name='tvmazeid'] | nZEDb:attr[@name='tvmazeid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -315,7 +315,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
tag = node->selectSingleNode("newznab:attr[@name='episode']");
tag = node->selectSingleNode("newznab:attr[@name='episode'] | nZEDb:attr[@name='episode']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -328,7 +328,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
tag = node->selectSingleNode("newznab:attr[@name='season']");
tag = node->selectSingleNode("newznab:attr[@name='season'] | nZEDb:attr[@name='season']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
@@ -339,7 +339,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
}
}
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr");
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr | nZEDb:attr");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
@@ -373,7 +373,7 @@ bool FeedFile::Parse()
if (ret != 0)
{
error("Failed to parse rss feed");
error("Failed to parse rss feed %s", *m_infoName);
return false;
}
@@ -407,7 +407,8 @@ void FeedFile::Parse_StartElement(const char *name, const char **atts)
}
}
}
else if (m_feedItemInfo && !strcmp("newznab:attr", name) &&
else if (m_feedItemInfo &&
(!strcmp("newznab:attr", name) || !strcmp("nZEDb:attr", name)) &&
atts[0] && atts[1] && atts[2] && atts[3] &&
!strcmp("name", atts[0]) && !strcmp("value", atts[2]))
{
@@ -592,6 +593,6 @@ void FeedFile::SAX_error(FeedFile* file, const char *msg, ...)
// remove trailing CRLF
for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
error("Error parsing rss feed: %s", errMsg);
error("Error parsing rss feed %s: %s", *file->m_infoName, errMsg);
}
#endif

View File

@@ -27,7 +27,7 @@
class FeedFile
{
public:
FeedFile(const char* fileName);
FeedFile(const char* fileName, const char* infoName);
bool Parse();
std::unique_ptr<FeedItemList> DetachFeedItems() { return std::move(m_feedItems); }
@@ -36,6 +36,7 @@ public:
private:
std::unique_ptr<FeedItemList> m_feedItems;
CString m_fileName;
CString m_infoName;
void ParseSubject(FeedItemInfo& feedItemInfo);
#ifdef WIN32

View File

@@ -23,16 +23,20 @@
#include "Util.h"
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) :
const char* filter, bool pauseNzb, const char* category, int priority, const char* extensions) :
m_backlog(backlog), m_interval(interval), m_pauseNzb(pauseNzb), m_priority(priority)
{
m_id = id;
m_name = name ? name : "";
if (m_name.Length() == 0)
{
m_name.Format("Feed%i", m_id);
}
m_url = url ? url : "";
m_filter = filter ? filter : "";
m_filterHash = Util::HashBJ96(m_filter, strlen(m_filter), 0);
m_category = category ? category : "";
m_feedScript = feedScript ? feedScript : "";
m_extensions = extensions ? extensions : "";
}

View File

@@ -38,7 +38,7 @@ public:
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);
const char* extensions);
int GetId() { return m_id; }
const char* GetName() { return m_name; }
const char* GetUrl() { return m_url; }
@@ -48,7 +48,7 @@ public:
bool GetPauseNzb() { return m_pauseNzb; }
const char* GetCategory() { return m_category; }
int GetPriority() { return m_priority; }
const char* GetFeedScript() { return m_feedScript; }
const char* GetExtensions() { return m_extensions; }
time_t GetLastUpdate() { return m_lastUpdate; }
void SetLastUpdate(time_t lastUpdate) { m_lastUpdate = lastUpdate; }
bool GetPreview() { return m_preview; }
@@ -73,7 +73,7 @@ private:
uint32 m_filterHash;
bool m_pauseNzb;
CString m_category;
CString m_feedScript;
CString m_extensions;
int m_priority;
time_t m_lastUpdate = 0;
bool m_preview = false;

View File

@@ -133,7 +133,7 @@ bool Frontend::ServerEditQueue(DownloadQueue::EEditAction action, int offset, in
}
else
{
return DownloadQueue::Guard()->EditEntry(id, action, offset, nullptr);
return DownloadQueue::Guard()->EditEntry(id, action, CString::FormatStr("%i", offset));
}
}

View File

@@ -738,7 +738,7 @@ void NCursesFrontend::PrintFilename(FileInfo * fileInfo, int row, bool selected)
BString<1024> nzbNiceName;
if (m_showNzbname)
{
nzbNiceName.Format("%s%c", fileInfo->GetNzbInfo()->GetName(), (int)PATH_SEPARATOR);
nzbNiceName.Format("%s%c", fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR);
}
BString<1024> text("%s%i%s%s%s %s%s (%s%s)%s", Brace1, fileInfo->GetId(),

View File

@@ -517,6 +517,10 @@ void CommandLineParser::InitCommandLine(int argc, const char* const_argv[])
{
m_editQueueAction = DownloadQueue::eaGroupParkDelete;
}
else if (!strcasecmp(optarg, "SF"))
{
m_editQueueAction = DownloadQueue::eaGroupSortFiles;
}
else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K") || !strcasecmp(optarg, "CP"))
{
// switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions
@@ -741,11 +745,11 @@ void CommandLineParser::PrintUsage(const char* com)
" 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"
" -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"
" -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"
@@ -772,7 +776,6 @@ void CommandLineParser::PrintUsage(const char* com)
" 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"
@@ -784,6 +787,7 @@ void CommandLineParser::PrintUsage(const char* com)
" CP <name> Set category and apply post-process parameters\n"
" N <name> Rename\n"
" M Merge\n"
" SF Sort inner files for optimal order\n"
" S <name> Split - create new group from selected files\n"
" O <name>=<value> Set post-process parameter\n"
" - for post-jobs (O):\n"

View File

@@ -373,7 +373,7 @@ bool Signature::ReadSignature()
{
hexSig[sigLen - 2] = '\0'; // trim trailing ",
}
for (; *hexSig && *(hexSig+1);)
while (*hexSig && *(hexSig+1) && output != m_signature + sizeof(m_signature))
{
uchar c1 = *hexSig++;
uchar c2 = *hexSig++;

View File

@@ -2,7 +2,7 @@
* 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>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -59,10 +59,13 @@ static const char* OPTION_RESTRICTEDUSERNAME = "RestrictedUsername";
static const char* OPTION_RESTRICTEDPASSWORD = "RestrictedPassword";
static const char* OPTION_ADDUSERNAME = "AddUsername";
static const char* OPTION_ADDPASSWORD = "AddPassword";
static const char* OPTION_FORMAUTH = "FormAuth";
static const char* OPTION_SECURECONTROL = "SecureControl";
static const char* OPTION_SECUREPORT = "SecurePort";
static const char* OPTION_SECURECERT = "SecureCert";
static const char* OPTION_SECUREKEY = "SecureKey";
static const char* OPTION_CERTSTORE = "CertStore";
static const char* OPTION_CERTCHECK = "CertCheck";
static const char* OPTION_AUTHORIZEDIP = "AuthorizedIP";
static const char* OPTION_ARTICLETIMEOUT = "ArticleTimeout";
static const char* OPTION_URLTIMEOUT = "UrlTimeout";
@@ -72,8 +75,10 @@ static const char* OPTION_RELOADQUEUE = "ReloadQueue";
static const char* OPTION_BROKENLOG = "BrokenLog";
static const char* OPTION_NZBLOG = "NzbLog";
static const char* OPTION_DECODE = "Decode";
static const char* OPTION_RETRIES = "Retries";
static const char* OPTION_RETRYINTERVAL = "RetryInterval";
static const char* OPTION_ARTICLERETRIES = "ArticleRetries";
static const char* OPTION_ARTICLEINTERVAL = "ArticleInterval";
static const char* OPTION_URLRETRIES = "UrlRetries";
static const char* OPTION_URLINTERVAL = "UrlInterval";
static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout";
static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial";
static const char* OPTION_URLCONNECTIONS = "UrlConnections";
@@ -87,13 +92,14 @@ static const char* OPTION_PARCHECK = "ParCheck";
static const char* OPTION_PARREPAIR = "ParRepair";
static const char* OPTION_PARSCAN = "ParScan";
static const char* OPTION_PARQUICK = "ParQuick";
static const char* OPTION_POSTSTRATEGY = "PostStrategy";
static const char* OPTION_FILENAMING = "FileNaming";
static const char* OPTION_PARRENAME = "ParRename";
static const char* OPTION_PARBUFFER = "ParBuffer";
static const char* OPTION_PARTHREADS = "ParThreads";
static const char* OPTION_RARRENAME = "RarRename";
static const char* OPTION_HEALTHCHECK = "HealthCheck";
static const char* OPTION_SCANSCRIPT = "ScanScript";
static const char* OPTION_QUEUESCRIPT = "QueueScript";
static const char* OPTION_FEEDSCRIPT = "FeedScript";
static const char* OPTION_DIRECTRENAME = "DirectRename";
static const char* OPTION_UMASK = "UMask";
static const char* OPTION_UPDATEINTERVAL = "UpdateInterval";
static const char* OPTION_CURSESNZBNAME = "CursesNzbName";
@@ -105,7 +111,8 @@ static const char* OPTION_WRITEBUFFER = "WriteBuffer";
static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval";
static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge";
static const char* OPTION_DISKSPACE = "DiskSpace";
static const char* OPTION_DUMPCORE = "DumpCore";
static const char* OPTION_CRASHTRACE = "CrashTrace";
static const char* OPTION_CRASHDUMP = "CrashDump";
static const char* OPTION_PARPAUSEQUEUE = "ParPauseQueue";
static const char* OPTION_SCRIPTPAUSEQUEUE = "ScriptPauseQueue";
static const char* OPTION_NZBCLEANUPDISK = "NzbCleanupDisk";
@@ -113,15 +120,17 @@ static const char* OPTION_PARTIMELIMIT = "ParTimeLimit";
static const char* OPTION_KEEPHISTORY = "KeepHistory";
static const char* OPTION_ACCURATERATE = "AccurateRate";
static const char* OPTION_UNPACK = "Unpack";
static const char* OPTION_DIRECTUNPACK = "DirectUnpack";
static const char* OPTION_UNPACKCLEANUPDISK = "UnpackCleanupDisk";
static const char* OPTION_UNRARCMD = "UnrarCmd";
static const char* OPTION_SEVENZIPCMD = "SevenZipCmd";
static const char* OPTION_UNPACKPASSFILE = "UnpackPassFile";
static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue";
static const char* OPTION_SCRIPTORDER = "ScriptOrder";
static const char* OPTION_POSTSCRIPT = "PostScript";
static const char* OPTION_EXTENSIONS = "Extensions";
static const char* OPTION_EXTCLEANUPDISK = "ExtCleanupDisk";
static const char* OPTION_PARIGNOREEXT = "ParIgnoreExt";
static const char* OPTION_UNPACKIGNOREEXT = "UnpackIgnoreExt";
static const char* OPTION_FEEDHISTORY = "FeedHistory";
static const char* OPTION_URLFORCE = "UrlForce";
static const char* OPTION_TIMECORRECTION = "TimeCorrection";
@@ -132,6 +141,7 @@ static const char* OPTION_SHELLOVERRIDE = "ShellOverride";
static const char* OPTION_MONTHLYQUOTA = "MonthlyQuota";
static const char* OPTION_QUOTASTARTDAY = "QuotaStartDay";
static const char* OPTION_DAILYQUOTA = "DailyQuota";
static const char* OPTION_REORDERFILES = "ReorderFiles";
// obsolete options
static const char* OPTION_POSTLOGKIND = "PostLogKind";
@@ -155,6 +165,9 @@ static const char* OPTION_RESETLOG = "ResetLog";
static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue";
static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk";
static const char* OPTION_HISTORYCLEANUPDISK = "HistoryCleanupDisk";
static const char* OPTION_SCANSCRIPT = "ScanScript";
static const char* OPTION_QUEUESCRIPT = "QueueScript";
static const char* OPTION_FEEDSCRIPT = "FeedScript";
const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" };
const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
@@ -188,10 +201,13 @@ bool Options::OptEntry::Restricted()
bool restricted = !strcasecmp(m_name, OPTION_CONTROLIP) ||
!strcasecmp(m_name, OPTION_CONTROLPORT) ||
!strcasecmp(m_name, OPTION_FORMAUTH) ||
!strcasecmp(m_name, OPTION_SECURECONTROL) ||
!strcasecmp(m_name, OPTION_SECUREPORT) ||
!strcasecmp(m_name, OPTION_SECURECERT) ||
!strcasecmp(m_name, OPTION_SECUREKEY) ||
!strcasecmp(m_name, OPTION_CERTSTORE) ||
!strcasecmp(m_name, OPTION_CERTCHECK) ||
!strcasecmp(m_name, OPTION_AUTHORIZEDIP) ||
!strcasecmp(m_name, OPTION_DAEMONUSERNAME) ||
!strcasecmp(m_name, OPTION_UMASK) ||
@@ -325,6 +341,7 @@ void Options::Init(const char* exeName, const char* configFilename, bool noConfi
return;
}
ConvertOldOptions(&m_optEntries);
InitOptions();
CheckOptions();
@@ -416,10 +433,13 @@ void Options::InitDefaults()
SetOption(OPTION_ADDUSERNAME, "");
SetOption(OPTION_ADDPASSWORD, "");
SetOption(OPTION_CONTROLPORT, "6789");
SetOption(OPTION_FORMAUTH, "no");
SetOption(OPTION_SECURECONTROL, "no");
SetOption(OPTION_SECUREPORT, "6791");
SetOption(OPTION_SECURECERT, "");
SetOption(OPTION_SECUREKEY, "");
SetOption(OPTION_CERTSTORE, "");
SetOption(OPTION_CERTCHECK, "no");
SetOption(OPTION_AUTHORIZEDIP, "");
SetOption(OPTION_ARTICLETIMEOUT, "60");
SetOption(OPTION_URLTIMEOUT, "60");
@@ -429,8 +449,10 @@ void Options::InitDefaults()
SetOption(OPTION_BROKENLOG, "yes");
SetOption(OPTION_NZBLOG, "yes");
SetOption(OPTION_DECODE, "yes");
SetOption(OPTION_RETRIES, "3");
SetOption(OPTION_RETRYINTERVAL, "10");
SetOption(OPTION_ARTICLERETRIES, "3");
SetOption(OPTION_ARTICLEINTERVAL, "10");
SetOption(OPTION_URLRETRIES, "3");
SetOption(OPTION_URLINTERVAL, "10");
SetOption(OPTION_TERMINATETIMEOUT, "600");
SetOption(OPTION_CONTINUEPARTIAL, "no");
SetOption(OPTION_URLCONNECTIONS, "4");
@@ -444,15 +466,16 @@ void Options::InitDefaults()
SetOption(OPTION_PARREPAIR, "yes");
SetOption(OPTION_PARSCAN, "extended");
SetOption(OPTION_PARQUICK, "yes");
SetOption(OPTION_POSTSTRATEGY, "sequential");
SetOption(OPTION_FILENAMING, "article");
SetOption(OPTION_PARRENAME, "yes");
SetOption(OPTION_PARBUFFER, "16");
SetOption(OPTION_PARTHREADS, "1");
SetOption(OPTION_RARRENAME, "yes");
SetOption(OPTION_HEALTHCHECK, "none");
SetOption(OPTION_DIRECTRENAME, "no");
SetOption(OPTION_SCRIPTORDER, "");
SetOption(OPTION_POSTSCRIPT, "");
SetOption(OPTION_SCANSCRIPT, "");
SetOption(OPTION_QUEUESCRIPT, "");
SetOption(OPTION_FEEDSCRIPT, "");
SetOption(OPTION_EXTENSIONS, "");
SetOption(OPTION_DAEMONUSERNAME, "root");
SetOption(OPTION_UMASK, "1000");
SetOption(OPTION_UPDATEINTERVAL, "200");
@@ -465,7 +488,8 @@ void Options::InitDefaults()
SetOption(OPTION_NZBDIRINTERVAL, "5");
SetOption(OPTION_NZBDIRFILEAGE, "60");
SetOption(OPTION_DISKSPACE, "250");
SetOption(OPTION_DUMPCORE, "no");
SetOption(OPTION_CRASHTRACE, "no");
SetOption(OPTION_CRASHDUMP, "no");
SetOption(OPTION_PARPAUSEQUEUE, "no");
SetOption(OPTION_SCRIPTPAUSEQUEUE, "no");
SetOption(OPTION_NZBCLEANUPDISK, "no");
@@ -473,6 +497,7 @@ void Options::InitDefaults()
SetOption(OPTION_KEEPHISTORY, "7");
SetOption(OPTION_ACCURATERATE, "no");
SetOption(OPTION_UNPACK, "no");
SetOption(OPTION_DIRECTUNPACK, "no");
SetOption(OPTION_UNPACKCLEANUPDISK, "no");
#ifdef WIN32
SetOption(OPTION_UNRARCMD, "unrar.exe");
@@ -485,6 +510,7 @@ void Options::InitDefaults()
SetOption(OPTION_UNPACKPAUSEQUEUE, "no");
SetOption(OPTION_EXTCLEANUPDISK, "");
SetOption(OPTION_PARIGNOREEXT, "");
SetOption(OPTION_UNPACKIGNOREEXT, "");
SetOption(OPTION_FEEDHISTORY, "7");
SetOption(OPTION_URLFORCE, "yes");
SetOption(OPTION_TIMECORRECTION, "0");
@@ -495,6 +521,7 @@ void Options::InitDefaults()
SetOption(OPTION_MONTHLYQUOTA, "0");
SetOption(OPTION_QUOTASTARTDAY, "1");
SetOption(OPTION_DAILYQUOTA, "0");
SetOption(OPTION_REORDERFILES, "no");
}
void Options::InitOptFile()
@@ -641,10 +668,7 @@ void Options::InitOptions()
m_configTemplate = GetOption(OPTION_CONFIGTEMPLATE);
m_scriptOrder = GetOption(OPTION_SCRIPTORDER);
m_postScript = GetOption(OPTION_POSTSCRIPT);
m_scanScript = GetOption(OPTION_SCANSCRIPT);
m_queueScript = GetOption(OPTION_QUEUESCRIPT);
m_feedScript = GetOption(OPTION_FEEDSCRIPT);
m_extensions = GetOption(OPTION_EXTENSIONS);
m_controlIp = GetOption(OPTION_CONTROLIP);
m_controlUsername = GetOption(OPTION_CONTROLUSERNAME);
m_controlPassword = GetOption(OPTION_CONTROLPASSWORD);
@@ -654,6 +678,7 @@ void Options::InitOptions()
m_addPassword = GetOption(OPTION_ADDPASSWORD);
m_secureCert = GetOption(OPTION_SECURECERT);
m_secureKey = GetOption(OPTION_SECUREKEY);
m_certStore = GetOption(OPTION_CERTSTORE);
m_authorizedIp = GetOption(OPTION_AUTHORIZEDIP);
m_lockFile = GetOption(OPTION_LOCKFILE);
m_daemonUsername = GetOption(OPTION_DAEMONUSERNAME);
@@ -663,14 +688,17 @@ void Options::InitOptions()
m_unpackPassFile = GetOption(OPTION_UNPACKPASSFILE);
m_extCleanupDisk = GetOption(OPTION_EXTCLEANUPDISK);
m_parIgnoreExt = GetOption(OPTION_PARIGNOREEXT);
m_unpackIgnoreExt = GetOption(OPTION_UNPACKIGNOREEXT);
m_shellOverride = GetOption(OPTION_SHELLOVERRIDE);
m_downloadRate = ParseIntValue(OPTION_DOWNLOADRATE, 10) * 1024;
m_articleTimeout = ParseIntValue(OPTION_ARTICLETIMEOUT, 10);
m_urlTimeout = ParseIntValue(OPTION_URLTIMEOUT, 10);
m_terminateTimeout = ParseIntValue(OPTION_TERMINATETIMEOUT, 10);
m_retries = ParseIntValue(OPTION_RETRIES, 10);
m_retryInterval = ParseIntValue(OPTION_RETRYINTERVAL, 10);
m_articleRetries = ParseIntValue(OPTION_ARTICLERETRIES, 10);
m_articleInterval = ParseIntValue(OPTION_ARTICLEINTERVAL, 10);
m_urlRetries = ParseIntValue(OPTION_URLRETRIES, 10);
m_urlInterval = ParseIntValue(OPTION_URLINTERVAL, 10);
m_controlPort = ParseIntValue(OPTION_CONTROLPORT, 10);
m_securePort = ParseIntValue(OPTION_SECUREPORT, 10);
m_urlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10);
@@ -710,6 +738,8 @@ void Options::InitOptions()
m_parRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues);
m_parQuick = (bool)ParseEnumValue(OPTION_PARQUICK, BoolCount, BoolNames, BoolValues);
m_parRename = (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues);
m_rarRename = (bool)ParseEnumValue(OPTION_RARRENAME, BoolCount, BoolNames, BoolValues);
m_directRename = (bool)ParseEnumValue(OPTION_DIRECTRENAME, BoolCount, BoolNames, BoolValues);
m_reloadQueue = (bool)ParseEnumValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues);
m_cursesNzbName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues);
m_cursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues);
@@ -717,16 +747,21 @@ void Options::InitOptions()
m_crcCheck = (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues);
m_directWrite = (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues);
m_decode = (bool)ParseEnumValue(OPTION_DECODE, BoolCount, BoolNames, BoolValues);
m_dumpCore = (bool)ParseEnumValue(OPTION_DUMPCORE, BoolCount, BoolNames, BoolValues);
m_crashTrace = (bool)ParseEnumValue(OPTION_CRASHTRACE, BoolCount, BoolNames, BoolValues);
m_crashDump = (bool)ParseEnumValue(OPTION_CRASHDUMP, BoolCount, BoolNames, BoolValues);
m_parPauseQueue = (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
m_scriptPauseQueue = (bool)ParseEnumValue(OPTION_SCRIPTPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
m_nzbCleanupDisk = (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues);
m_accurateRate = (bool)ParseEnumValue(OPTION_ACCURATERATE, BoolCount, BoolNames, BoolValues);
m_formAuth = (bool)ParseEnumValue(OPTION_FORMAUTH, BoolCount, BoolNames, BoolValues);
m_secureControl = (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues);
m_unpack = (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues);
m_directUnpack = (bool)ParseEnumValue(OPTION_DIRECTUNPACK, BoolCount, BoolNames, BoolValues);
m_unpackCleanupDisk = (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues);
m_unpackPauseQueue = (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues);
m_urlForce = (bool)ParseEnumValue(OPTION_URLFORCE, BoolCount, BoolNames, BoolValues);
m_certCheck = (bool)ParseEnumValue(OPTION_CERTCHECK, BoolCount, BoolNames, BoolValues);
m_reorderFiles = (bool)ParseEnumValue(OPTION_REORDERFILES, BoolCount, BoolNames, BoolValues);
const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" };
const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses };
@@ -743,6 +778,16 @@ void Options::InitOptions()
const int ParScanCount = 4;
m_parScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues);
const char* PostStrategyNames[] = { "sequential", "balanced", "aggressive", "rocket" };
const int PostStrategyValues[] = { ppSequential, ppBalanced, ppAggressive, ppRocket };
const int PostStrategyCount = 4;
m_postStrategy = (EPostStrategy)ParseEnumValue(OPTION_POSTSTRATEGY, PostStrategyCount, PostStrategyNames, PostStrategyValues);
const char* FileNamingNames[] = { "auto", "article", "nzb" };
const int FileNamingValues[] = { nfAuto, nfArticle, nfNzb };
const int FileNamingCount = 4;
m_fileNaming = (EFileNaming)ParseEnumValue(OPTION_FILENAMING, FileNamingCount, FileNamingNames, FileNamingValues);
const char* HealthCheckNames[] = { "pause", "delete", "park", "none" };
const int HealthCheckValues[] = { hcPause, hcDelete, hcPark, hcNone };
const int HealthCheckCount = 4;
@@ -965,6 +1010,16 @@ void Options::InitServers()
m_tls |= tls;
}
const char* nipversion = GetOption(BString<100>("Server%i.IpVersion", n));
int ipversion = 0;
if (nipversion)
{
const char* IpVersionNames[] = {"auto", "ipv4", "ipv6"};
const int IpVersionValues[] = {0, 4, 6};
const int IpVersionCount = 3;
ipversion = ParseEnumValue(BString<100>("Server%i.IpVersion", n), IpVersionCount, IpVersionNames, IpVersionValues);
}
const char* ncipher = GetOption(BString<100>("Server%i.Cipher", n));
const char* nconnections = GetOption(BString<100>("Server%i.Connections", n));
const char* nretention = GetOption(BString<100>("Server%i.Retention", n));
@@ -985,6 +1040,7 @@ void Options::InitServers()
m_extender->AddNewsServer(n, active, nname,
nhost,
nport ? atoi(nport) : 119,
ipversion,
nusername, npassword,
joinGroup, tls, ncipher,
nconnections ? atoi(nconnections) : 1,
@@ -1018,10 +1074,10 @@ void Options::InitCategories()
unpack = (bool)ParseEnumValue(BString<100>("Category%i.Unpack", n), BoolCount, BoolNames, BoolValues);
}
const char* npostscript = GetOption(BString<100>("Category%i.PostScript", n));
const char* nextensions = GetOption(BString<100>("Category%i.Extensions", n));
const char* naliases = GetOption(BString<100>("Category%i.Aliases", n));
bool definition = nname || ndestdir || nunpack || npostscript || naliases;
bool definition = nname || ndestdir || nunpack || nextensions || naliases;
bool completed = nname && strlen(nname) > 0;
if (!definition)
@@ -1037,7 +1093,7 @@ void Options::InitCategories()
CheckDir(destDir, BString<100>("Category%i.DestDir", n), m_destDir, false, false);
}
m_categories.emplace_back(nname, destDir, unpack, npostscript);
m_categories.emplace_back(nname, destDir, unpack, nextensions);
Category& category = m_categories.back();
// split Aliases into tokens and create items for each token
@@ -1068,7 +1124,7 @@ void Options::InitFeeds()
const char* nurl = GetOption(BString<100>("Feed%i.URL", n));
const char* nfilter = GetOption(BString<100>("Feed%i.Filter", n));
const char* ncategory = GetOption(BString<100>("Feed%i.Category", n));
const char* nfeedscript = GetOption(BString<100>("Feed%i.FeedScript", n));
const char* nextensions = GetOption(BString<100>("Feed%i.Extensions", n));
const char* nbacklog = GetOption(BString<100>("Feed%i.Backlog", n));
bool backlog = true;
@@ -1088,7 +1144,7 @@ void Options::InitFeeds()
const char* npriority = GetOption(BString<100>("Feed%i.Priority", n));
bool definition = nname || nurl || nfilter || ncategory || nbacklog || npausenzb ||
ninterval || npriority || nfeedscript;
ninterval || npriority || nextensions;
bool completed = nurl;
if (!definition)
@@ -1101,7 +1157,7 @@ void Options::InitFeeds()
if (m_extender)
{
m_extender->AddFeed(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter,
backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nfeedscript);
backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nextensions);
}
}
else
@@ -1170,13 +1226,6 @@ void Options::InitScheduler()
continue;
}
int weekDaysVal = 0;
if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal))
{
ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", n, weekDays);
continue;
}
if (taskCommand == scDownloadRate)
{
if (param)
@@ -1207,36 +1256,60 @@ void Options::InitScheduler()
continue;
}
int hours, minutes;
Tokenizer tok(time, ";,");
while (const char* oneTime = tok.Next())
{
if (!ParseTime(oneTime, &hours, &minutes))
{
ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", n, oneTime);
break;
}
CreateSchedulerTask(n, time, weekDays, taskCommand, param);
}
}
if (m_extender)
void Options::CreateSchedulerTask(int id, const char* time, const char* weekDays,
ESchedulerCommand command, const char* param)
{
if (!id)
{
m_configLine = 0;
}
int weekDaysVal = 0;
if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal))
{
ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", id, weekDays);
return;
}
int hours, minutes;
Tokenizer tok(time, ";,");
while (const char* oneTime = tok.Next())
{
if (!ParseTime(oneTime, &hours, &minutes))
{
ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", id, oneTime);
return;
}
if (m_extender)
{
if (hours == -2)
{
if (hours == -1)
for (int everyHour = 0; everyHour < 24; everyHour++)
{
for (int everyHour = 0; everyHour < 24; everyHour++)
{
m_extender->AddTask(n, everyHour, minutes, weekDaysVal, taskCommand, param);
}
}
else
{
m_extender->AddTask(n, hours, minutes, weekDaysVal, taskCommand, param);
m_extender->AddTask(id, everyHour, minutes, weekDaysVal, command, param);
}
}
else
{
m_extender->AddTask(id, hours, minutes, weekDaysVal, command, param);
}
}
}
}
bool Options::ParseTime(const char* time, int* hours, int* minutes)
{
if (!strcmp(time, "*"))
{
*hours = -1;
return true;
}
int colons = 0;
const char* p = time;
while (*p)
@@ -1265,7 +1338,7 @@ bool Options::ParseTime(const char* time, int* hours, int* minutes)
if (time[0] == '*')
{
*hours = -1;
*hours = -2;
}
else
{
@@ -1464,7 +1537,8 @@ bool Options::ValidateOptionName(const char* optname, const char* optvalue)
!strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") ||
!strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections") ||
!strcasecmp(p, ".cipher") || !strcasecmp(p, ".group") ||
!strcasecmp(p, ".retention") || !strcasecmp(p, ".optional")))
!strcasecmp(p, ".retention") || !strcasecmp(p, ".optional") ||
!strcasecmp(p, ".notes") || !strcasecmp(p, ".ipversion")))
{
return true;
}
@@ -1486,7 +1560,7 @@ bool Options::ValidateOptionName(const char* optname, const char* optvalue)
{
char* p = (char*)optname + 8;
while (*p >= '0' && *p <= '9') p++;
if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".postscript") ||
if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".extensions") ||
!strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases")))
{
return true;
@@ -1499,7 +1573,7 @@ bool Options::ValidateOptionName(const char* optname, const char* optvalue)
while (*p >= '0' && *p <= '9') p++;
if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") ||
!strcasecmp(p, ".filter") || !strcasecmp(p, ".backlog") || !strcasecmp(p, ".pausenzb") ||
!strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".feedscript")))
!strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".extensions")))
{
return true;
}
@@ -1532,21 +1606,27 @@ bool Options::ValidateOptionName(const char* optname, const char* optvalue)
ConfigWarn("Option \"%s\" is obsolete, ignored", optname);
return true;
}
if (!strcasecmp(optname, OPTION_POSTPROCESS) ||
!strcasecmp(optname, OPTION_NZBPROCESS) ||
!strcasecmp(optname, OPTION_NZBADDEDPROCESS))
{
if (optvalue && strlen(optvalue) > 0)
{
ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead", optname, OPTION_SCRIPTDIR,
!strcasecmp(optname, OPTION_POSTPROCESS) ? OPTION_POSTSCRIPT :
!strcasecmp(optname, OPTION_NZBPROCESS) ? OPTION_SCANSCRIPT :
!strcasecmp(optname, OPTION_NZBADDEDPROCESS) ? OPTION_QUEUESCRIPT :
"ERROR");
ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead",
optname, OPTION_SCRIPTDIR, OPTION_EXTENSIONS);
}
return true;
}
if (!strcasecmp(optname, OPTION_SCANSCRIPT) ||
!strcasecmp(optname, OPTION_QUEUESCRIPT) ||
!strcasecmp(optname, OPTION_FEEDSCRIPT))
{
// will be automatically converted into "Extensions"
return true;
}
if (!strcasecmp(optname, OPTION_CREATELOG) || !strcasecmp(optname, OPTION_RESETLOG))
{
ConfigWarn("Option \"%s\" is obsolete, ignored, use \"%s\" instead", optname, OPTION_WRITELOG);
@@ -1600,16 +1680,22 @@ void Options::ConvertOldOption(CString& option, CString& value)
value = "extended";
}
if (!strcasecmp(option, "DefScript"))
if (!strcasecmp(option, "DefScript") || !strcasecmp(option, "PostScript"))
{
option = "PostScript";
option = "Extensions";
}
int nameLen = strlen(option);
if (!strncasecmp(option, "Category", 8) && nameLen > 10 &&
!strcasecmp(option + nameLen - 10, ".DefScript"))
if (!strncasecmp(option, "Category", 8) &&
((nameLen > 10 && !strcasecmp(option + nameLen - 10, ".DefScript")) ||
(nameLen > 11 && !strcasecmp(option + nameLen - 11, ".PostScript"))))
{
option.Replace(".DefScript", ".PostScript");
option.Replace(".DefScript", ".Extensions");
option.Replace(".PostScript", ".Extensions");
}
if (!strncasecmp(option, "Feed", 4) && nameLen > 11 && !strcasecmp(option + nameLen - 11, ".FeedScript"))
{
option.Replace(".FeedScript", ".Extensions");
}
if (!strcasecmp(option, "WriteBufferSize"))
@@ -1625,10 +1711,25 @@ void Options::ConvertOldOption(CString& option, CString& value)
option = "ArticleTimeout";
}
if (!strcasecmp(option, "Retries"))
{
option = "ArticleRetries";
}
if (!strcasecmp(option, "RetryInterval"))
{
option = "ArticleInterval";
}
if (!strcasecmp(option, "CreateBrokenLog"))
{
option = "BrokenLog";
}
if (!strcasecmp(option, "DumpCore"))
{
option = OPTION_CRASHDUMP;
}
}
void Options::CheckOptions()
@@ -1644,6 +1745,11 @@ void Options::CheckOptions()
LocateOptionSrcPos(OPTION_PARRENAME);
ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARRENAME);
}
if (m_directRename)
{
LocateOptionSrcPos(OPTION_DIRECTRENAME);
ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_DIRECTRENAME);
}
#endif
#ifdef DISABLE_CURSES
@@ -1660,8 +1766,43 @@ void Options::CheckOptions()
LocateOptionSrcPos(OPTION_SECURECONTROL);
ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_SECURECONTROL);
}
if (m_certCheck)
{
LocateOptionSrcPos(OPTION_CERTCHECK);
ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_CERTCHECK);
}
#endif
#ifdef HAVE_OPENSSL
#ifndef HAVE_X509_CHECK_HOST
if (m_certCheck)
{
LocateOptionSrcPos(OPTION_CERTCHECK);
ConfigWarn("TLS certificate verification (option \"%s\") is limited because the program "
"was compiled with older OpenSSL version not supporting hostname validation (at least OpenSSL 1.0.2d is required)", OPTION_CERTCHECK);
}
#endif
#endif
#ifdef HAVE_LIBGNUTLS
#if GNUTLS_VERSION_NUMBER < 0x030104
if (m_certCheck)
{
LocateOptionSrcPos(OPTION_CERTCHECK);
ConfigWarn("TLS certificate verification (option \"%s\") is disabled because the program "
"was compiled with older GnuTLS version not supporting certificate validation (at least GnuTLS 3.1.4 is required)", OPTION_CERTCHECK);
}
#endif
#endif
if (m_certCheck && m_certStore.Empty())
{
LocateOptionSrcPos(OPTION_CERTCHECK);
ConfigError("Option \"%s\" requires proper configuration of option \"%s\"", OPTION_CERTCHECK, OPTION_CERTSTORE);
m_certCheck = false;
}
if (!m_decode)
{
m_directWrite = false;
@@ -1696,7 +1837,7 @@ void Options::CheckOptions()
if (sizeof(void*) == 4 && m_parBuffer + m_articleCache > 1900)
{
ConfigError("Options \"ArticleCache\" and \"ParBuffer\" in total cannot use more than 1900MB of memory in 32-Bit mode. Changed to 1500 and 400");
m_articleCache = 1900;
m_articleCache = 1500;
m_parBuffer = 400;
}
@@ -1705,3 +1846,77 @@ void Options::CheckOptions()
ConfigError("Invalid value for option \"UnpackPassFile\": %s. File not found", *m_unpackPassFile);
}
}
void Options::ConvertOldOptions(OptEntries* optEntries)
{
MergeOldScriptOption(optEntries, OPTION_SCANSCRIPT, true);
MergeOldScriptOption(optEntries, OPTION_QUEUESCRIPT, true);
MergeOldScriptOption(optEntries, OPTION_FEEDSCRIPT, false);
}
void Options::MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories)
{
OptEntry* optEntry = optEntries->FindOption(optname);
if (!optEntry || Util::EmptyStr(optEntry->GetValue()))
{
return;
}
OptEntry* extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
if (!extensionsOpt)
{
optEntries->emplace_back(OPTION_EXTENSIONS, "");
extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
}
const char* scriptList = optEntry->GetValue();
Tokenizer tok(scriptList, ",;");
while (const char* scriptName = tok.Next())
{
// merge into global "Extensions"
if (!HasScript(extensionsOpt->m_value, scriptName))
{
if (!extensionsOpt->m_value.Empty())
{
extensionsOpt->m_value.Append(",");
}
extensionsOpt->m_value.Append(scriptName);
}
// merge into categories' "Extensions" (if not empty)
if (mergeCategories)
{
for (OptEntry& opt : optEntries)
{
const char* optname = opt.GetName();
if (!strncasecmp(optname, "category", 8))
{
char* p = (char*)optname + 8;
while (*p >= '0' && *p <= '9') p++;
if (p && (!strcasecmp(p, ".extensions")))
{
if (!opt.m_value.Empty() && !HasScript(opt.m_value, scriptName))
{
opt.m_value.Append(",");
opt.m_value.Append(scriptName);
}
}
}
}
}
}
}
bool Options::HasScript(const char* scriptList, const char* scriptName)
{
Tokenizer tok(scriptList, ",;");
while (const char* scriptName2 = tok.Next())
{
if (!strcasecmp(scriptName2, scriptName))
{
return true;
}
}
return false;
};

View File

@@ -85,6 +85,19 @@ public:
scDeactivateServer,
scFetchFeed
};
enum EPostStrategy
{
ppSequential,
ppBalanced,
ppAggressive,
ppRocket
};
enum EFileNaming
{
nfAuto,
nfArticle,
nfNzb
};
class OptEntry
{
@@ -126,19 +139,19 @@ public:
class Category
{
public:
Category(const char* name, const char* destDir, bool unpack, const char* postScript) :
m_name(name), m_destDir(destDir), m_unpack(unpack), m_postScript(postScript) {}
Category(const char* name, const char* destDir, bool unpack, const char* extensions) :
m_name(name), m_destDir(destDir), m_unpack(unpack), m_extensions(extensions) {}
const char* GetName() { return m_name; }
const char* GetDestDir() { return m_destDir; }
bool GetUnpack() { return m_unpack; }
const char* GetPostScript() { return m_postScript; }
const char* GetExtensions() { return m_extensions; }
NameList* GetAliases() { return &m_aliases; }
private:
CString m_name;
CString m_destDir;
bool m_unpack;
CString m_postScript;
CString m_extensions;
NameList m_aliases;
};
@@ -154,12 +167,12 @@ public:
{
public:
virtual void AddNewsServer(int id, bool active, const char* name, const char* host,
int port, const char* user, const char* pass, bool joinGroup,
int port, int ipVersion, const char* user, const char* pass, bool joinGroup,
bool tls, const char* cipher, int maxConnections, int retention,
int level, int group, bool optional) = 0;
virtual void AddFeed(int id, const char* name, const char* url, int interval,
const char* filter, bool backlog, bool pauseNzb, const char* category,
int priority, const char* feedScript) {}
int priority, const char* extensions) {}
virtual void AddTask(int id, int hours, int minutes, int weekDaysBits, ESchedulerCommand command,
const char* param) {}
virtual void SetupFirstStart() {}
@@ -170,9 +183,12 @@ public:
Options(CmdOptList* commandLineOptions, Extender* extender);
~Options();
bool SplitOptionString(const char* option, CString& optName, CString& optValue);
static bool SplitOptionString(const char* option, CString& optName, CString& optValue);
static void ConvertOldOptions(OptEntries* optEntries);
bool GetFatalError() { return m_fatalError; }
GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); }
void CreateSchedulerTask(int id, const char* time, const char* weekDays,
ESchedulerCommand command, const char* param);
// Options
const char* GetConfigFilename() { return m_configFilename; }
@@ -200,8 +216,10 @@ public:
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; }
int GetArticleRetries() { return m_articleRetries; }
int GetArticleInterval() { return m_articleInterval; }
int GetUrlRetries() { return m_urlRetries; }
int GetUrlInterval() { return m_urlInterval; }
bool GetSaveQueue() { return m_saveQueue; }
bool GetFlushQueue() { return m_flushQueue; }
bool GetDupeCheck() { return m_dupeCheck; }
@@ -213,10 +231,13 @@ public:
const char* GetAddUsername() { return m_addUsername; }
const char* GetAddPassword() { return m_addPassword; }
int GetControlPort() { return m_controlPort; }
bool GetFormAuth() { return m_formAuth; }
bool GetSecureControl() { return m_secureControl; }
int GetSecurePort() { return m_securePort; }
const char* GetSecureCert() { return m_secureCert; }
const char* GetSecureKey() { return m_secureKey; }
const char* GetCertStore() { return m_certStore; }
bool GetCertCheck() { return m_certCheck; }
const char* GetAuthorizedIp() { return m_authorizedIp; }
const char* GetLockFile() { return m_lockFile; }
const char* GetDaemonUsername() { return m_daemonUsername; }
@@ -231,15 +252,14 @@ public:
bool GetParRepair() { return m_parRepair; }
EParScan GetParScan() { return m_parScan; }
bool GetParQuick() { return m_parQuick; }
EPostStrategy GetPostStrategy() { return m_postStrategy; }
bool GetParRename() { return m_parRename; }
int GetParBuffer() { return m_parBuffer; }
int GetParThreads() { return m_parThreads; }
bool GetRarRename() { return m_rarRename; }
EHealthCheck GetHealthCheck() { return m_healthCheck; }
const char* GetScriptOrder() { return m_scriptOrder; }
const char* GetPostScript() { return m_postScript; }
const char* GetScanScript() { return m_scanScript; }
const char* GetQueueScript() { return m_queueScript; }
const char* GetFeedScript() { return m_feedScript; }
const char* GetExtensions() { return m_extensions; }
int GetUMask() { return m_umask; }
int GetUpdateInterval() {return m_updateInterval; }
bool GetCursesNzbName() { return m_cursesNzbName; }
@@ -252,7 +272,8 @@ public:
int GetNzbDirFileAge() { return m_nzbDirFileAge; }
int GetDiskSpace() { return m_diskSpace; }
bool GetTls() { return m_tls; }
bool GetDumpCore() { return m_dumpCore; }
bool GetCrashTrace() { return m_crashTrace; }
bool GetCrashDump() { return m_crashDump; }
bool GetParPauseQueue() { return m_parPauseQueue; }
bool GetScriptPauseQueue() { return m_scriptPauseQueue; }
bool GetNzbCleanupDisk() { return m_nzbCleanupDisk; }
@@ -260,6 +281,7 @@ public:
int GetKeepHistory() { return m_keepHistory; }
bool GetAccurateRate() { return m_accurateRate; }
bool GetUnpack() { return m_unpack; }
bool GetDirectUnpack() { return m_directUnpack; }
bool GetUnpackCleanupDisk() { return m_unpackCleanupDisk; }
const char* GetUnrarCmd() { return m_unrarCmd; }
const char* GetSevenZipCmd() { return m_sevenZipCmd; }
@@ -267,6 +289,7 @@ public:
bool GetUnpackPauseQueue() { return m_unpackPauseQueue; }
const char* GetExtCleanupDisk() { return m_extCleanupDisk; }
const char* GetParIgnoreExt() { return m_parIgnoreExt; }
const char* GetUnpackIgnoreExt() { return m_unpackIgnoreExt; }
int GetFeedHistory() { return m_feedHistory; }
bool GetUrlForce() { return m_urlForce; }
int GetTimeCorrection() { return m_timeCorrection; }
@@ -277,6 +300,9 @@ public:
int GetMonthlyQuota() { return m_monthlyQuota; }
int GetQuotaStartDay() { return m_quotaStartDay; }
int GetDailyQuota() { return m_dailyQuota; }
bool GetDirectRename() { return m_directRename; }
bool GetReorderFiles() { return m_reorderFiles; }
EFileNaming GetFileNaming() { return m_fileNaming; }
Categories* GetCategories() { return &m_categories; }
Category* FindCategory(const char* name, bool searchAliases) { return m_categories.FindCategory(name, searchAliases); }
@@ -343,8 +369,10 @@ private:
int m_terminateTimeout = 0;
bool m_appendCategoryDir = false;
bool m_continuePartial = false;
int m_retries = 0;
int m_retryInterval = 0;
int m_articleRetries = 0;
int m_articleInterval = 0;
int m_urlRetries = 0;
int m_urlInterval = 0;
bool m_saveQueue = false;
bool m_flushQueue = false;
bool m_dupeCheck = false;
@@ -355,11 +383,14 @@ private:
CString m_restrictedPassword;
CString m_addUsername;
CString m_addPassword;
bool m_formAuth = false;
int m_controlPort = 0;
bool m_secureControl = false;
int m_securePort = 0;
CString m_secureCert;
CString m_secureKey;
CString m_certStore;
bool m_certCheck = false;
CString m_authorizedIp;
CString m_lockFile;
CString m_daemonUsername;
@@ -374,15 +405,15 @@ private:
bool m_parRepair = false;
EParScan m_parScan = psLimited;
bool m_parQuick = true;
EPostStrategy m_postStrategy = ppSequential;
bool m_parRename = false;
int m_parBuffer = 0;
int m_parThreads = 0;
bool m_rarRename = false;
bool m_directRename = false;
EHealthCheck m_healthCheck = hcNone;
CString m_postScript;
CString m_extensions;
CString m_scriptOrder;
CString m_scanScript;
CString m_queueScript;
CString m_feedScript;
int m_umask = 0;
int m_updateInterval = 0;
bool m_cursesNzbName = false;
@@ -395,7 +426,8 @@ private:
int m_nzbDirFileAge = 0;
int m_diskSpace = 0;
bool m_tls = false;
bool m_dumpCore = false;
bool m_crashTrace = false;
bool m_crashDump = false;
bool m_parPauseQueue = false;
bool m_scriptPauseQueue = false;
bool m_nzbCleanupDisk = false;
@@ -403,6 +435,7 @@ private:
int m_keepHistory = 0;
bool m_accurateRate = false;
bool m_unpack = false;
bool m_directUnpack = false;
bool m_unpackCleanupDisk = false;
CString m_unrarCmd;
CString m_sevenZipCmd;
@@ -410,6 +443,7 @@ private:
bool m_unpackPauseQueue;
CString m_extCleanupDisk;
CString m_parIgnoreExt;
CString m_unpackIgnoreExt;
int m_feedHistory = 0;
bool m_urlForce = false;
int m_timeCorrection = 0;
@@ -420,6 +454,8 @@ private:
int m_monthlyQuota = 0;
int m_quotaStartDay = 0;
int m_dailyQuota = 0;
bool m_reorderFiles = false;
EFileNaming m_fileNaming = nfArticle;
// Current state
bool m_serverMode = false;
@@ -461,7 +497,9 @@ private:
void ConfigError(const char* msg, ...);
void ConfigWarn(const char* msg, ...);
void LocateOptionSrcPos(const char *optionName);
void ConvertOldOption(CString& option, CString& value);
static void ConvertOldOption(CString& option, CString& value);
static void MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories);
static bool HasScript(const char* scriptList, const char* scriptName);
};
extern Options* g_Options;

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2008-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -93,7 +93,10 @@ void Scheduler::CheckTasks()
for (Task* task : &m_taskList)
{
task->m_lastExecuted = 0;
if (task->m_hours != Task::STARTUP_TASK)
{
task->m_lastExecuted = 0;
}
}
}
@@ -133,9 +136,8 @@ void Scheduler::CheckTasks()
}
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);
bool doTask = (task->m_hours >= 0 && weekDayOK && localLastCheck < appoint && appoint <= localCurrent) ||
(task->m_hours == Task::STARTUP_TASK && task->m_lastExecuted == 0);
if (doTask)
{
@@ -162,6 +164,8 @@ void Scheduler::ExecuteTask(Task* task)
"Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" };
debug("Executing scheduled command: %s", commandName[task->m_command]);
bool executeProcess = m_executeProcess || task->m_hours == Task::STARTUP_TASK;
switch (task->m_command)
{
case scDownloadRate:
@@ -190,9 +194,9 @@ void Scheduler::ExecuteTask(Task* task)
m_pauseScanChanged = true;
break;
case scScript:
case scExtensions:
case scProcess:
if (m_executeProcess)
if (executeProcess)
{
SchedulerScriptController::StartScript(task->m_param, task->m_command == scProcess, task->m_id);
}
@@ -204,7 +208,7 @@ void Scheduler::ExecuteTask(Task* task)
break;
case scFetchFeed:
if (m_executeProcess)
if (executeProcess)
{
FetchFeed(task->m_param);
break;

View File

@@ -35,7 +35,7 @@ public:
scPausePostProcess,
scUnpausePostProcess,
scDownloadRate,
scScript,
scExtensions,
scProcess,
scPauseScan,
scUnpauseScan,
@@ -52,7 +52,7 @@ public:
m_id(id), m_hours(hours), m_minutes(minutes),
m_weekDaysBits(weekDaysBits), m_command(command), m_param(param) {}
friend class Scheduler;
static const int STARTUP_TASK = -1;
private:
int m_id;
int m_hours;

View File

@@ -147,17 +147,20 @@ LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* exPtrs)
#ifdef DEBUG
PrintBacktrace(exPtrs->ContextRecord);
#else
info("Detailed exception information can be printed by debug version of NZBGet (available from download page)");
#endif
ExitProcess(-1);
return EXCEPTION_CONTINUE_SEARCH;
#else
info("Detailed crash information can be printed by debug version of NZBGet."
" For more info visit http://nzbget.net/crash-dump");
return EXCEPTION_EXECUTE_HANDLER;
#endif
}
void InstallErrorHandler()
{
SetUnhandledExceptionFilter(ExceptionFilter);
if (g_Options->GetCrashTrace())
{
SetUnhandledExceptionFilter(ExceptionFilter);
}
}
#else
@@ -171,7 +174,7 @@ std::vector<sighandler> SignalProcList;
/**
* activates the creation of core-files
*/
void EnableDumpCore()
void EnableCoreDump()
{
rlimit rlim;
rlim.rlim_cur= RLIM_INFINITY;
@@ -248,9 +251,9 @@ void SignalProc(int signum)
void InstallErrorHandler()
{
#ifdef HAVE_SYS_PRCTL_H
if (g_Options->GetDumpCore())
if (g_Options->GetCrashDump())
{
EnableDumpCore();
EnableCoreDump();
}
#endif
@@ -258,7 +261,10 @@ void InstallErrorHandler()
signal(SIGTERM, SignalProc);
signal(SIGPIPE, SIG_IGN);
#ifdef DEBUG
signal(SIGSEGV, SignalProc);
if (g_Options->GetCrashTrace())
{
signal(SIGSEGV, SignalProc);
}
#endif
#ifdef SIGCHLD_HANDLER
// it could be necessary on some systems to activate a handler for SIGCHLD

View File

@@ -2,7 +2,7 @@
* 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>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,7 +39,6 @@
#include "PrePostProcessor.h"
#include "HistoryCoordinator.h"
#include "DupeCoordinator.h"
#include "ParChecker.h"
#include "Scheduler.h"
#include "Scanner.h"
#include "FeedCoordinator.h"
@@ -52,6 +51,7 @@
#include "Util.h"
#include "FileSystem.h"
#include "StackTrace.h"
#include "CommandScript.h"
#ifdef WIN32
#include "WinService.h"
#include "WinConsole.h"
@@ -60,6 +60,9 @@
#ifdef ENABLE_TESTS
#include "TestMain.h"
#endif
#ifndef DISABLE_NSERV
#include "NServMain.h"
#endif
// Prototypes
void RunMain();
@@ -82,6 +85,7 @@ ArticleCache* g_ArticleCache;
QueueScriptCoordinator* g_QueueScriptCoordinator;
ServiceCoordinator* g_ServiceCoordinator;
ScriptConfig* g_ScriptConfig;
CommandScriptLog* g_CommandScriptLog;
#ifdef WIN32
WinConsole* g_WinConsole;
#endif
@@ -127,6 +131,16 @@ int main(int argc, char *argv[], char *argp[])
TestCleanup();
#endif
if (argc > 1 && (!strcmp(argv[1], "--nserv")))
{
#ifndef DISABLE_NSERV
return NServMain(argc, argv);
#else
printf("ERROR: Could not start NServ, the program was compiled without NServ\n");
return 1;
#endif
}
#ifdef WIN32
InstallUninstallServiceCheck(argc, argv);
#endif
@@ -138,6 +152,7 @@ int main(int argc, char *argv[], char *argp[])
{
if (!strcmp(argv[i], "-D"))
{
AllocConsole(); // needed for sending CTRL+BREAK signal to child processes
StartService(RunMain);
return 0;
}
@@ -160,7 +175,7 @@ public:
// Options::Extender
virtual void AddNewsServer(int id, bool active, const char* name, const char* host,
int port, const char* user, const char* pass, bool joinGroup,
int port, int ipVersion, const char* user, const char* pass, bool joinGroup,
bool tls, const char* cipher, int maxConnections, int retention,
int level, int group, bool optional);
virtual void AddFeed(int id, const char* name, const char* url, int interval,
@@ -191,6 +206,7 @@ private:
std::unique_ptr<QueueScriptCoordinator> m_queueScriptCoordinator;
std::unique_ptr<ServiceCoordinator> m_serviceCoordinator;
std::unique_ptr<ScriptConfig> m_scriptConfig;
std::unique_ptr<CommandScriptLog> m_commandScriptLog;
#ifdef WIN32
std::unique_ptr<WinConsole> m_winConsole;
#endif
@@ -266,6 +282,9 @@ void NZBGet::Init()
m_scanner->InitOptions();
m_queueScriptCoordinator->InitOptions();
#ifndef DISABLE_TLS
TlsSocket::InitOptions(g_Options->GetCertCheck() ? g_Options->GetCertStore() : nullptr);
#endif
if (m_commandLineParser->GetDaemonMode())
{
@@ -351,6 +370,9 @@ void NZBGet::CreateGlobals()
m_scriptConfig = std::make_unique<ScriptConfig>();
g_ScriptConfig = m_scriptConfig.get();
m_commandScriptLog = std::make_unique<CommandScriptLog>();
g_CommandScriptLog = m_commandScriptLog.get();
m_scheduler = std::make_unique<Scheduler>();
m_diskService = std::make_unique<DiskService>();
@@ -368,7 +390,7 @@ void NZBGet::BootConfig()
if (m_commandLineParser->GetPrintUsage() || m_commandLineParser->GetErrors() || g_ArgumentCount <= 1)
{
m_commandLineParser->PrintUsage(((const char**)(*g_Arguments))[0]);
exit(0);
exit(m_commandLineParser->GetPrintUsage() ? 0 : 1);
}
debug("Reading options");
@@ -394,7 +416,7 @@ void NZBGet::BootConfig()
}
m_serverPool->SetTimeout(m_options->GetArticleTimeout());
m_serverPool->SetRetryInterval(m_options->GetRetryInterval());
m_serverPool->SetRetryInterval(m_options->GetArticleInterval());
m_scriptConfig->InitOptions();
}
@@ -423,6 +445,7 @@ void NZBGet::Cleanup()
g_QueueScriptCoordinator = nullptr;
g_Maintenance = nullptr;
g_StatMeter = nullptr;
g_CommandScriptLog = nullptr;
#ifdef WIN32
g_WinConsole = nullptr;
#endif
@@ -680,106 +703,109 @@ void NZBGet::Run(bool reload)
void NZBGet::ProcessClientRequest()
{
RemoteClient Client;
bool ok = false;
switch (m_commandLineParser->GetClientOperation())
{
case CommandLineParser::opClientRequestListFiles:
Client.RequestServerList(true, false, m_commandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? m_commandLineParser->GetEditQueueText() : nullptr);
ok = Client.RequestServerList(true, false, m_commandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? m_commandLineParser->GetEditQueueText() : nullptr);
break;
case CommandLineParser::opClientRequestListGroups:
Client.RequestServerList(false, true, m_commandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? m_commandLineParser->GetEditQueueText() : nullptr);
ok = Client.RequestServerList(false, true, m_commandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? m_commandLineParser->GetEditQueueText() : nullptr);
break;
case CommandLineParser::opClientRequestListStatus:
Client.RequestServerList(false, false, nullptr);
ok = Client.RequestServerList(false, false, nullptr);
break;
case CommandLineParser::opClientRequestDownloadPause:
Client.RequestServerPauseUnpause(true, rpDownload);
ok = Client.RequestServerPauseUnpause(true, rpDownload);
break;
case CommandLineParser::opClientRequestDownloadUnpause:
Client.RequestServerPauseUnpause(false, rpDownload);
ok = Client.RequestServerPauseUnpause(false, rpDownload);
break;
case CommandLineParser::opClientRequestSetRate:
Client.RequestServerSetDownloadRate(m_commandLineParser->GetSetRate());
ok = Client.RequestServerSetDownloadRate(m_commandLineParser->GetSetRate());
break;
case CommandLineParser::opClientRequestDumpDebug:
Client.RequestServerDumpDebug();
ok = Client.RequestServerDumpDebug();
break;
case CommandLineParser::opClientRequestEditQueue:
Client.RequestServerEditQueue((DownloadQueue::EEditAction)m_commandLineParser->GetEditQueueAction(),
ok = Client.RequestServerEditQueue((DownloadQueue::EEditAction)m_commandLineParser->GetEditQueueAction(),
m_commandLineParser->GetEditQueueOffset(), m_commandLineParser->GetEditQueueText(),
m_commandLineParser->GetEditQueueIdList(), m_commandLineParser->GetEditQueueNameList(),
(ERemoteMatchMode)m_commandLineParser->GetMatchMode());
break;
case CommandLineParser::opClientRequestLog:
Client.RequestServerLog(m_commandLineParser->GetLogLines());
ok = Client.RequestServerLog(m_commandLineParser->GetLogLines());
break;
case CommandLineParser::opClientRequestShutdown:
Client.RequestServerShutdown();
ok = Client.RequestServerShutdown();
break;
case CommandLineParser::opClientRequestReload:
Client.RequestServerReload();
ok = Client.RequestServerReload();
break;
case CommandLineParser::opClientRequestDownload:
Client.RequestServerDownload(m_commandLineParser->GetAddNzbFilename(), m_commandLineParser->GetArgFilename(),
ok = Client.RequestServerDownload(m_commandLineParser->GetAddNzbFilename(), m_commandLineParser->GetArgFilename(),
m_commandLineParser->GetAddCategory(), m_commandLineParser->GetAddTop(), m_commandLineParser->GetAddPaused(), m_commandLineParser->GetAddPriority(),
m_commandLineParser->GetAddDupeKey(), m_commandLineParser->GetAddDupeMode(), m_commandLineParser->GetAddDupeScore());
break;
case CommandLineParser::opClientRequestVersion:
Client.RequestServerVersion();
ok = Client.RequestServerVersion();
break;
case CommandLineParser::opClientRequestPostQueue:
Client.RequestPostQueue();
ok = Client.RequestPostQueue();
break;
case CommandLineParser::opClientRequestWriteLog:
Client.RequestWriteLog(m_commandLineParser->GetWriteLogKind(), m_commandLineParser->GetLastArg());
ok = Client.RequestWriteLog(m_commandLineParser->GetWriteLogKind(), m_commandLineParser->GetLastArg());
break;
case CommandLineParser::opClientRequestScanAsync:
Client.RequestScan(false);
ok = Client.RequestScan(false);
break;
case CommandLineParser::opClientRequestScanSync:
Client.RequestScan(true);
ok = Client.RequestScan(true);
break;
case CommandLineParser::opClientRequestPostPause:
Client.RequestServerPauseUnpause(true, rpPostProcess);
ok = Client.RequestServerPauseUnpause(true, rpPostProcess);
break;
case CommandLineParser::opClientRequestPostUnpause:
Client.RequestServerPauseUnpause(false, rpPostProcess);
ok = Client.RequestServerPauseUnpause(false, rpPostProcess);
break;
case CommandLineParser::opClientRequestScanPause:
Client.RequestServerPauseUnpause(true, rpScan);
ok = Client.RequestServerPauseUnpause(true, rpScan);
break;
case CommandLineParser::opClientRequestScanUnpause:
Client.RequestServerPauseUnpause(false, rpScan);
ok = Client.RequestServerPauseUnpause(false, rpScan);
break;
case CommandLineParser::opClientRequestHistory:
case CommandLineParser::opClientRequestHistoryAll:
Client.RequestHistory(m_commandLineParser->GetClientOperation() == CommandLineParser::opClientRequestHistoryAll);
ok = Client.RequestHistory(m_commandLineParser->GetClientOperation() == CommandLineParser::opClientRequestHistoryAll);
break;
case CommandLineParser::opClientNoOperation:
break;
return;
}
exit(ok ? 0 : 1);
}
void NZBGet::ProcessWebGet()
@@ -865,16 +891,12 @@ void NZBGet::Daemonize()
// obtain a new process group
setsid();
// close all descriptors
for (int i = getdtablesize(); i >= 0; --i)
{
close(i);
}
// handle standart I/O
int d = open("/dev/null", O_RDWR);
dup(d);
dup(d);
dup2(d, 0);
dup2(d, 1);
dup2(d, 2);
close(d);
// set up lock-file
int lfp = -1;
@@ -928,10 +950,10 @@ void NZBGet::Daemonize()
#endif
void NZBGet::AddNewsServer(int id, bool active, const char* name, const char* host,
int port, const char* user, const char* pass, bool joinGroup, bool tls,
int port, int ipVersion, const char* user, const char* pass, bool joinGroup, bool tls,
const char* cipher, int maxConnections, int retention, int level, int group, bool optional)
{
m_serverPool->AddServer(std::make_unique<NewsServer>(id, active, name, host, port, user, pass, joinGroup,
m_serverPool->AddServer(std::make_unique<NewsServer>(id, active, name, host, port, ipVersion, user, pass, joinGroup,
tls, cipher, maxConnections, retention, level, group, optional));
}

View File

@@ -40,6 +40,9 @@
//#define HAVE_LIBGNUTLS
#endif
/* Define to 1 if OpenSSL supports function "X509_check_host". */
#define HAVE_X509_CHECK_HOST 1
/* Define to the name of macro which returns the name of function being
compiled */
#define FUNCTION_MACRO_NAME __FUNCTION__
@@ -196,6 +199,7 @@ using namespace MSXML;
#include <iostream>
#include <fstream>
#include <memory>
#include <functional>
#ifdef HAVE_LIBGNUTLS
#ifdef WIN32
@@ -204,12 +208,16 @@ typedef SSIZE_T ssize_t;
typedef int pid_t;
#endif
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#if GNUTLS_VERSION_NUMBER <= 0x020b00
#define NEED_GCRYPT_LOCKING
#endif
#ifdef NEED_GCRYPT_LOCKING
#include <gcrypt.h>
#endif /* NEED_GCRYPT_LOCKING */
#include <nettle/sha.h>
#include <nettle/pbkdf2.h>
#include <nettle/aes.h>
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
@@ -218,6 +226,7 @@ typedef int pid_t;
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#endif /* HAVE_OPENSSL */
#ifdef HAVE_REGEX_H
@@ -318,12 +327,16 @@ typedef int pid_t;
#ifdef HAVE_STDINT_H
typedef uint8_t uint8;
typedef int16_t int16;
typedef uint16_t uint16;
typedef uint32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#else
typedef unsigned char uint8;
typedef signed short int16;
typedef unsigned short uint16;
typedef signed int int32;
typedef unsigned int uint32;
typedef signed long long int64;

View File

@@ -63,8 +63,8 @@ void ArticleDownloader::SetInfoName(const char* infoName)
- if download fails with error "Not-Found" (article or group not found) or with CRC error,
add the server to failed server list;
- if download fails with general failure error (article incomplete, other unknown error
codes), try the same server again as many times as defined by option <Retries>; if all attempts
fail, add the server to failed server list;
codes), try the same server again as many times as defined by option <ArticleRetries>;
if all attempts fail, add the server to failed server list;
- if all servers from current level were tried, increase level;
- if all servers from all levels were tried, break the loop with failure status.
<end-loop>
@@ -80,7 +80,7 @@ void ArticleDownloader::Run()
m_articleWriter.Prepare();
EStatus status = adFailed;
int retries = g_Options->GetRetries() > 0 ? g_Options->GetRetries() : 1;
int retries = g_Options->GetArticleRetries() > 0 ? g_Options->GetArticleRetries() : 1;
int remainedRetries = retries;
ServerPool::RawServerList failedServers;
failedServers.reserve(g_ServerPool->GetServers()->size());
@@ -296,6 +296,11 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
m_writingStarted = false;
m_articleInfo->SetCrc(0);
if (m_contentAnalyzer)
{
m_contentAnalyzer->Reset();
}
if (m_connection->GetNewsServer()->GetJoinGroup())
{
// change group
@@ -316,14 +321,7 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
}
// retrieve article
for (int retry = 3; retry > 0; retry--)
{
response = m_connection->Request(BString<1024>("ARTICLE %s\r\n", m_articleInfo->GetMessageId()));
if ((response && !strncmp(response, "2", 1)) || m_connection->GetAuthError())
{
break;
}
}
response = m_connection->Request(BString<1024>("ARTICLE %s\r\n", m_articleInfo->GetMessageId()));
status = CheckResponse(response, "could not fetch article");
if (status != adFinished)
@@ -548,6 +546,11 @@ bool ArticleDownloader::Write(char* line, int len)
bool ok = len == 0 || m_articleWriter.Write(line, len);
if (m_contentAnalyzer)
{
m_contentAnalyzer->Append(line, len);
}
return ok;
}

View File

@@ -29,6 +29,15 @@
#include "NntpConnection.h"
#include "Decoder.h"
#include "ArticleWriter.h"
#include "Util.h"
class ArticleContentAnalyzer
{
public:
virtual ~ArticleContentAnalyzer() {};
virtual void Reset() = 0;
virtual void Append(const void* buffer, int len) = 0;
};
class ArticleDownloader : public Thread, public Subject
{
@@ -77,6 +86,8 @@ public:
void SetConnection(NntpConnection* connection) { m_connection = connection; }
void CompleteFileParts() { m_articleWriter.CompleteFileParts(); }
int GetDownloadedSize() { return m_downloadedSize; }
void SetContentAnalyzer(std::unique_ptr<ArticleContentAnalyzer> contentAnalyzer) { m_contentAnalyzer = std::move(contentAnalyzer); }
ArticleContentAnalyzer* GetContentAnalyzer() { return m_contentAnalyzer.get(); }
void LogDebugInfo();
@@ -97,6 +108,7 @@ private:
ServerStatList m_serverStats;
bool m_writingStarted;
int m_downloadedSize = 0;
std::unique_ptr<ArticleContentAnalyzer> m_contentAnalyzer;
EStatus Download();
EStatus DecodeCheck();

View File

@@ -84,7 +84,7 @@ bool ArticleWriter::Start(Decoder::EFormat format, const char* filename, int64 f
}
if (!outputInitialized && filename &&
FileSystem::FileExists(m_fileInfo->GetNzbInfo()->GetDestDir(), filename))
FileSystem::FileExists(BString<1024>("%s%c%s", m_fileInfo->GetNzbInfo()->GetDestDir(), PATH_SEPARATOR, filename)))
{
m_duplicate = true;
return false;
@@ -275,7 +275,7 @@ void ArticleWriter::BuildOutputFilename()
else
{
filename.Format("%s%c%i.out.tmp", m_fileInfo->GetNzbInfo()->GetDestDir(),
(int)PATH_SEPARATOR, m_fileInfo->GetId());
PATH_SEPARATOR, m_fileInfo->GetId());
m_fileInfo->SetOutputFilename(filename);
}
@@ -292,14 +292,16 @@ void ArticleWriter::CompleteFileParts()
BString<1024> nzbName;
BString<1024> nzbDestDir;
BString<1024> filename;
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
nzbName = m_fileInfo->GetNzbInfo()->GetName();
nzbDestDir = m_fileInfo->GetNzbInfo()->GetDestDir();
filename = m_fileInfo->GetFilename();
}
BString<1024> infoFilename("%s%c%s", *nzbName, (int)PATH_SEPARATOR, m_fileInfo->GetFilename());
BString<1024> infoFilename("%s%c%s", *nzbName, PATH_SEPARATOR, *filename);
bool cached = m_fileInfo->GetCachedArticles() > 0;
@@ -332,11 +334,11 @@ void ArticleWriter::CompleteFileParts()
CString ofn;
if (m_fileInfo->GetForceDirectWrite())
{
ofn.Format("%s%c%s", *nzbDestDir, PATH_SEPARATOR, m_fileInfo->GetFilename());
ofn.Format("%s%c%s", *nzbDestDir, PATH_SEPARATOR, *filename);
}
else
{
ofn = FileSystem::MakeUniqueFilename(nzbDestDir, m_fileInfo->GetFilename());
ofn = FileSystem::MakeUniqueFilename(nzbDestDir, *filename);
}
DiskFile outfile;
@@ -436,14 +438,14 @@ void ArticleWriter::CompleteFileParts()
m_fileInfo->SetFailedArticles(m_fileInfo->GetFailedArticles() + 1);
m_fileInfo->SetSuccessArticles(m_fileInfo->GetSuccessArticles() - 1);
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Could not find file %s for %s%c%s [%i/%i]",
pa->GetResultFilename(), *nzbName, (int)PATH_SEPARATOR, m_fileInfo->GetFilename(),
pa->GetPartNumber(), (int)m_fileInfo->GetArticles()->size());
"Could not find file %s for %s [%i/%i]",
pa->GetResultFilename(), *infoFilename, pa->GetPartNumber(),
(int)m_fileInfo->GetArticles()->size());
}
}
else if (!g_Options->GetDecode())
{
BString<1024> dstFileName("%s%c%03i", *ofn, (int)PATH_SEPARATOR, pa->GetPartNumber());
BString<1024> dstFileName("%s%c%03i", *ofn, PATH_SEPARATOR, pa->GetPartNumber());
if (!FileSystem::MoveFile(pa->GetResultFilename(), dstFileName))
{
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
@@ -523,11 +525,11 @@ void ArticleWriter::CompleteFileParts()
if (g_Options->GetBrokenLog())
{
BString<1024> brokenLogName("%s%c_brokenlog.txt", *nzbDestDir, (int)PATH_SEPARATOR);
BString<1024> brokenLogName("%s%c_brokenlog.txt", *nzbDestDir, PATH_SEPARATOR);
DiskFile file;
if (file.Open(brokenLogName, DiskFile::omAppend))
{
file.Print("%s (%i/%i)%s", m_fileInfo->GetFilename(), m_fileInfo->GetSuccessArticles(),
file.Print("%s (%i/%i)%s", *filename, m_fileInfo->GetSuccessArticles(),
m_fileInfo->GetTotalArticles(), LINE_ENDING);
file.Close();
}
@@ -538,11 +540,25 @@ void ArticleWriter::CompleteFileParts()
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkInfo, "Partially downloaded %s", *infoFilename);
}
m_fileInfo->SetCrc(crc);
m_fileInfo->SetOutputFilename(ofn);
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_fileInfo->SetCrc(crc);
m_fileInfo->SetOutputFilename(ofn);
if (strcmp(m_fileInfo->GetFilename(), filename))
{
// file was renamed during completion, need to move the file
ofn = FileSystem::MakeUniqueFilename(nzbDestDir, m_fileInfo->GetFilename());
if (!FileSystem::MoveFile(m_fileInfo->GetOutputFilename(), ofn))
{
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Could not rename file %s to %s: %s", m_fileInfo->GetOutputFilename(),
*ofn, *FileSystem::GetLastErrorMessage());
}
m_fileInfo->SetOutputFilename(ofn);
}
if (strcmp(m_fileInfo->GetNzbInfo()->GetDestDir(), nzbDestDir))
{
// destination directory was changed during completion, need to move the file
@@ -593,6 +609,9 @@ void ArticleWriter::FlushCache()
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Could not open file %s: %s", m_fileInfo->GetOutputFilename(),
*FileSystem::GetLastErrorMessage());
// prevent multiple error messages
pa->DiscardSegment();
flushedArticles++;
break;
}
needBufFile = true;
@@ -608,6 +627,9 @@ void ArticleWriter::FlushCache()
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Could not create file %s: %s", *destFile,
*FileSystem::GetLastErrorMessage());
// prevent multiple error messages
pa->DiscardSegment();
flushedArticles++;
break;
}
needBufFile = true;
@@ -674,14 +696,14 @@ bool ArticleWriter::MoveCompletedFiles(NzbInfo* nzbInfo, const char* oldDestDir)
// move already downloaded files to new destination
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
BString<1024> oldFileName("%s%c%s", oldDestDir, (int)PATH_SEPARATOR, completedFile.GetFileName());
BString<1024> newFileName("%s%c%s", nzbInfo->GetDestDir(), (int)PATH_SEPARATOR, completedFile.GetFileName());
BString<1024> oldFileName("%s%c%s", oldDestDir, PATH_SEPARATOR, completedFile.GetFilename());
BString<1024> newFileName("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, completedFile.GetFilename());
// check if file was not moved already
if (strcmp(oldFileName, newFileName))
{
// prevent overwriting of existing files
newFileName = FileSystem::MakeUniqueFilename(nzbInfo->GetDestDir(), completedFile.GetFileName());
newFileName = FileSystem::MakeUniqueFilename(nzbInfo->GetDestDir(), completedFile.GetFilename());
detail("Moving file %s to %s", *oldFileName, *newFileName);
if (!FileSystem::MoveFile(oldFileName, newFileName))
@@ -695,10 +717,10 @@ bool ArticleWriter::MoveCompletedFiles(NzbInfo* nzbInfo, const char* oldDestDir)
// move brokenlog.txt
if (g_Options->GetBrokenLog())
{
BString<1024> oldBrokenLogName("%s%c_brokenlog.txt", oldDestDir, (int)PATH_SEPARATOR);
BString<1024> oldBrokenLogName("%s%c_brokenlog.txt", oldDestDir, PATH_SEPARATOR);
if (FileSystem::FileExists(oldBrokenLogName))
{
BString<1024> brokenLogName("%s%c_brokenlog.txt", nzbInfo->GetDestDir(), (int)PATH_SEPARATOR);
BString<1024> brokenLogName("%s%c_brokenlog.txt", nzbInfo->GetDestDir(), PATH_SEPARATOR);
detail("Moving file %s to %s", *oldBrokenLogName, *brokenLogName);
if (FileSystem::FileExists(brokenLogName))
@@ -871,7 +893,7 @@ bool ArticleCache::CheckFlush(bool flushEverything)
if (fileInfo->GetCachedArticles() > 0 && (fileInfo->GetActiveDownloads() == 0 || flushEverything))
{
m_fileInfo = fileInfo;
infoName.Format("%s%c%s", m_fileInfo->GetNzbInfo()->GetName(), (int)PATH_SEPARATOR, m_fileInfo->GetFilename());
infoName.Format("%s%c%s", m_fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR, m_fileInfo->GetFilename());
break;
}
}

View File

@@ -22,10 +22,10 @@
#include "nzbget.h"
#include "NewsServer.h"
NewsServer::NewsServer(int id, bool active, const char* name, const char* host, int port,
NewsServer::NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
const char* user, const char* pass, bool joinGroup, bool tls, const char* cipher,
int maxConnections, int retention, int level, int group, bool optional) :
m_id(id), m_active(active), m_port(port), m_level(level), m_normLevel(level),
m_id(id), m_active(active), m_port(port), m_ipVersion(ipVersion), 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)

View File

@@ -32,7 +32,7 @@
class NewsServer
{
public:
NewsServer(int id, bool active, const char* name, const char* host, int port,
NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
const char* user, const char* pass, bool joinGroup,
bool tls, const char* cipher, int maxConnections, int retention,
int level, int group, bool optional);
@@ -45,6 +45,7 @@ public:
int GetGroup() { return m_group; }
const char* GetHost() { return m_host; }
int GetPort() { return m_port; }
int GetIpVersion() { return m_ipVersion; }
const char* GetUser() { return m_user; }
const char* GetPassword() { return m_password; }
int GetMaxConnections() { return m_maxConnections; }
@@ -67,6 +68,7 @@ private:
int m_group;
CString m_host;
int m_port;
int m_ipVersion;
CString m_user;
CString m_password;
int m_maxConnections;

View File

@@ -27,10 +27,13 @@
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
NntpConnection::NntpConnection(NewsServer* newsServer) : Connection(newsServer->GetHost(), newsServer->GetPort(), newsServer->GetTls()), m_newsServer(newsServer)
NntpConnection::NntpConnection(NewsServer* newsServer) :
Connection(newsServer->GetHost(), newsServer->GetPort(), newsServer->GetTls()), m_newsServer(newsServer)
{
m_lineBuf.Reserve(CONNECTION_LINEBUFFER_SIZE);
SetCipher(newsServer->GetCipher());
SetIPVersion(newsServer->GetIpVersion() == 4 ? Connection::ipV4 :
newsServer->GetIpVersion() == 6 ? Connection::ipV6 : Connection::ipAuto);
}
const char* NntpConnection::Request(const char* req)

View File

@@ -0,0 +1,146 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "NServFrontend.h"
#include "Util.h"
NServFrontend::NServFrontend()
{
#ifdef WIN32
m_console = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
}
void NServFrontend::Run()
{
while (!IsStopped())
{
Update();
usleep(100 * 1000);
}
// Printing the last messages
Update();
}
void NServFrontend::Update()
{
BeforePrint();
{
GuardedMessageList messages = g_Log->GuardMessages();
if (!messages->empty())
{
Message& firstMessage = messages->front();
int start = m_neededLogFirstId - firstMessage.GetId() + 1;
if (start < 0)
{
PrintSkip();
start = 0;
}
for (uint32 i = (uint32)start; i < messages->size(); i++)
{
PrintMessage(messages->at(i));
m_neededLogFirstId = messages->at(i).GetId();
}
}
}
fflush(stdout);
}
void NServFrontend::BeforePrint()
{
if (m_needGoBack)
{
// go back one line
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
GetConsoleScreenBufferInfo(m_console, &BufInfo);
BufInfo.dwCursorPosition.Y--;
SetConsoleCursorPosition(m_console, BufInfo.dwCursorPosition);
#else
printf("\r\033[1A");
#endif
m_needGoBack = false;
}
}
void NServFrontend::PrintMessage(Message& message)
{
#ifdef WIN32
switch (message.GetKind())
{
case Message::mkDebug:
SetConsoleTextAttribute(m_console, 8);
printf("[DEBUG] ");
break;
case Message::mkError:
SetConsoleTextAttribute(m_console, 4);
printf("[ERROR] ");
break;
case Message::mkWarning:
SetConsoleTextAttribute(m_console, 5);
printf("[WARNING]");
break;
case Message::mkInfo:
SetConsoleTextAttribute(m_console, 2);
printf("[INFO] ");
break;
case Message::mkDetail:
SetConsoleTextAttribute(m_console, 2);
printf("[DETAIL]");
break;
}
SetConsoleTextAttribute(m_console, 7);
CString msg = message.GetText();
CharToOem(msg, msg);
printf(" %s\n", *msg);
#else
const char* msg = message.GetText();
switch (message.GetKind())
{
case Message::mkDebug:
printf("[DEBUG] %s\033[K\n", msg);
break;
case Message::mkError:
printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg);
break;
case Message::mkWarning:
printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg);
break;
case Message::mkInfo:
printf("\033[32m[INFO]\033[39m %s\033[K\n", msg);
break;
case Message::mkDetail:
printf("\033[32m[DETAIL]\033[39m %s\033[K\n", msg);
break;
}
#endif
}
void NServFrontend::PrintSkip()
{
#ifdef WIN32
printf(".....\n");
#else
printf(".....\033[K\n");
#endif
}

View File

@@ -0,0 +1,48 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NSERVFRONTEND_H
#define NSERVFRONTEND_H
#include "Thread.h"
#include "Log.h"
class NServFrontend : public Thread
{
public:
NServFrontend();
private:
uint32 m_neededLogEntries = 0;
uint32 m_neededLogFirstId = 0;
bool m_needGoBack = false;
#ifdef WIN32
HANDLE m_console;
#endif
void Run();
void Update();
void BeforePrint();
void PrintMessage(Message& message);
void PrintSkip();
};
#endif

246
daemon/nserv/NServMain.cpp Normal file
View File

@@ -0,0 +1,246 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "Thread.h"
#include "Connection.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
#include "NServFrontend.h"
#include "NntpServer.h"
#include "NzbGenerator.h"
#include "Options.h"
struct NServOpts
{
CString dataDir;
CString cacheDir;
CString bindAddress;
int firstPort;
int instances;
CString logFile;
CString secureCert;
CString secureKey;
BString<1024> logOpt;
bool generateNzb;
int segmentSize;
bool quit;
NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts);
};
void NServPrintUsage(const char* com);
int NServMain(int argc, char* argv[])
{
Log log;
info("NServ %s (Test NNTP server)", Util::VersionRevision());
Options::CmdOptList cmdOpts;
NServOpts opts(argc, argv, cmdOpts);
if (opts.dataDir.Empty())
{
NServPrintUsage(argv[0]);
return 1;
}
if (!FileSystem::DirectoryExists(opts.dataDir))
{
// dataDir does not exist. Let's find out a bit more, and report:
if (FileSystem::FileExists(opts.dataDir))
{
error("Specified data-dir %s is not a directory, but a file", *opts.dataDir );
} else {
error("Specified data-dir %s does not exist", *opts.dataDir );
}
}
Options options(&cmdOpts, nullptr);
log.InitOptions();
Thread::Init();
Connection::Init();
#ifndef DISABLE_TLS
TlsSocket::Init();
#endif
NServFrontend frontend;
frontend.Start();
if (opts.generateNzb)
{
NzbGenerator gen(opts.dataDir, opts.segmentSize);
gen.Execute();
if (opts.quit)
{
return 0;
}
}
CString errmsg;
if (opts.cacheDir && !FileSystem::ForceDirectories(opts.cacheDir, errmsg))
{
error("Could not create directory %s: %s", *opts.cacheDir, *errmsg);
}
std::vector<std::unique_ptr<NntpServer>> instances;
for (int i = 0; i < opts.instances; i++)
{
instances.emplace_back(std::make_unique<NntpServer>(i + 1, opts.bindAddress,
opts.firstPort + i, opts.secureCert, opts.secureKey, opts.dataDir, opts.cacheDir));
instances.back()->Start();
}
info("Press Ctrl+C to quit");
while (getchar()) usleep(1000*200);
for (std::unique_ptr<NntpServer>& serv: instances)
{
serv->Stop();
}
frontend.Stop();
bool hasRunning = false;
do
{
hasRunning = frontend.IsRunning();
for (std::unique_ptr<NntpServer>& serv : instances)
{
hasRunning |= serv->IsRunning();
}
usleep(50 * 1000);
} while (hasRunning);
return 0;
}
void NServPrintUsage(const char* com)
{
printf("Usage:\n"
" %s --nserv -d <data-dir> [optional switches] \n"
" -d <data-dir> - directory whose files will be served\n"
" Optional switches:\n"
" -c <cache-dir> - directory to store encoded articles\n"
" -l <log-file> - write into log-file (disabled by default)\n"
" -i <instances> - number of server instances (default is 1)\n"
" -b <address> - ip address to bind to (default is 0.0.0.0)\n"
" -p <port> - port number for the first instance (default is 6791)\n"
" -s <cert> <key> - paths to SSL certificate and key files\n"
" -v <verbose> - verbosity level 0..3 (default is 2)\n"
" -z <seg-size> - generate nzbs for all files in data-dir (size in bytes)\n"
" -q - quit after generating nzbs (in combination with -z)\n"
, FileSystem::BaseFileName(com));
}
NServOpts::NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts)
{
instances = 1;
bindAddress = "0.0.0.0";
firstPort = 6791;
generateNzb = false;
segmentSize = 500000;
quit = false;
int verbosity = 2;
char short_options[] = "b:c:d:l:p:i:s:v:z:q";
optind = 2;
while (true)
{
int c = getopt(argc, argv, short_options);
if (c == -1) break;
switch (c)
{
case 'd':
dataDir = optind > argc ? nullptr : argv[optind - 1];
break;
case 'c':
cacheDir = optind > argc ? nullptr : argv[optind - 1];
break;
case 'l':
logFile = optind > argc ? nullptr : argv[optind - 1];
break;
case 'b':
bindAddress= optind > argc ? "0.0.0.0" : argv[optind - 1];
break;
case 'p':
firstPort = atoi(optind > argc ? "6791" : argv[optind - 1]);
break;
case 's':
secureCert = optind > argc ? nullptr : argv[optind - 1];
optind++;
secureKey = optind > argc ? nullptr : argv[optind - 1];
break;
case 'i':
instances = atoi(optind > argc ? "1" : argv[optind - 1]);
break;
case 'v':
verbosity = atoi(optind > argc ? "1" : argv[optind - 1]);
break;
case 'z':
generateNzb = true;
segmentSize = atoi(optind > argc ? "500000" : argv[optind - 1]);
break;
case 'q':
quit = true;
break;
}
}
if (logFile.Empty())
{
cmdOpts.push_back("WriteLog=none");
}
else
{
cmdOpts.push_back("WriteLog=append");
logOpt.Format("LogFile=%s", *logFile);
cmdOpts.push_back(logOpt);
}
if (verbosity < 1)
{
cmdOpts.push_back("InfoTarget=none");
cmdOpts.push_back("WarningTarget=none");
cmdOpts.push_back("ErrorTarget=none");
}
if (verbosity < 2)
{
cmdOpts.push_back("DetailTarget=none");
}
if (verbosity > 2)
{
cmdOpts.push_back("DebugTarget=both");
}
}

26
daemon/nserv/NServMain.h Normal file
View File

@@ -0,0 +1,26 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NSERVMAIN_H
#define NSERVMAIN_H
int NServMain(int argc, char * argv[]);
#endif

321
daemon/nserv/NntpServer.cpp Normal file
View File

@@ -0,0 +1,321 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "NntpServer.h"
#include "Log.h"
#include "Util.h"
#include "YEncoder.h"
class NntpProcessor : public Thread
{
public:
NntpProcessor(int id, int serverId, const char* dataDir, const char* cacheDir,
const char* secureCert, const char* secureKey) :
m_id(id), m_serverId(serverId), m_dataDir(dataDir), m_cacheDir(cacheDir),
m_secureCert(secureCert), m_secureKey(secureKey) {}
~NntpProcessor() { m_connection->Disconnect(); }
virtual void Run();
void SetConnection(std::unique_ptr<Connection>&& connection) { m_connection = std::move(connection); }
private:
int m_id;
int m_serverId;
std::unique_ptr<Connection> m_connection;
const char* m_dataDir;
const char* m_cacheDir;
const char* m_secureCert;
const char* m_secureKey;
const char* m_messageid;
CString m_filename;
int m_part;
int64 m_offset;
int m_size;
bool m_sendHeaders;
void ServArticle();
void SendSegment();
bool ServerInList(const char* servList);
};
void NntpServer::Run()
{
debug("Entering NntpServer-loop");
info("Listening on port %i", m_port);
int num = 1;
while (!IsStopped())
{
bool bind = true;
if (!m_connection)
{
m_connection = std::make_unique<Connection>(m_host, m_port, m_secureCert);
m_connection->SetTimeout(10);
m_connection->SetSuppressErrors(false);
bind = m_connection->Bind();
}
// Accept connections and store the new Connection
std::unique_ptr<Connection> acceptedConnection;
if (bind)
{
acceptedConnection = m_connection->Accept();
}
if (!bind || !acceptedConnection)
{
// Server could not bind or accept connection, waiting 1/2 sec and try again
if (IsStopped())
{
break;
}
m_connection.reset();
usleep(500 * 1000);
continue;
}
NntpProcessor* commandThread = new NntpProcessor(num++, m_id,
m_dataDir, m_cacheDir, m_secureCert, m_secureKey);
commandThread->SetAutoDestroy(true);
commandThread->SetConnection(std::move(acceptedConnection));
commandThread->Start();
}
if (m_connection)
{
m_connection->Disconnect();
}
debug("Exiting NntpServer-loop");
}
void NntpServer::Stop()
{
Thread::Stop();
if (m_connection)
{
m_connection->SetSuppressErrors(true);
m_connection->Cancel();
#ifdef WIN32
m_connection->Disconnect();
#endif
}
}
void NntpProcessor::Run()
{
m_connection->SetSuppressErrors(false);
#ifndef DISABLE_TLS
if (m_secureCert && !m_connection->StartTls(false, m_secureCert, m_secureKey))
{
error("Could not establish secure connection to nntp-client: Start TLS failed");
return;
}
#endif
m_connection->WriteLine("200 Welcome (NServ)\r\n");
CharBuffer buf(1024);
int bytesRead = 0;
while (CString line = m_connection->ReadLine(buf, 1024, &bytesRead))
{
line.TrimRight();
detail("[%i] Received: %s", m_id, *line);
if (!strncasecmp(line, "ARTICLE ", 8))
{
m_messageid = line + 8;
m_sendHeaders = true;
ServArticle();
}
else if (!strncasecmp(line, "BODY ", 5))
{
m_messageid = line + 5;
m_sendHeaders = false;
ServArticle();
}
else if (!strncasecmp(line, "GROUP ", 6))
{
m_connection->WriteLine(CString::FormatStr("211 0 0 0 %s\r\n", line + 7));
}
else if (!strncasecmp(line, "AUTHINFO ", 9))
{
m_connection->WriteLine("281 Authentication accepted\r\n");
}
else if (!strcasecmp(line, "QUIT"))
{
detail("[%i] Closing connection", m_id);
m_connection->WriteLine("205 Connection closing\r\n");
break;
}
else
{
warn("[%i] Unknown command: %s", m_id, *line);
m_connection->WriteLine("500 Unknown command\r\n");
}
}
m_connection->SetGracefull(true);
m_connection->Disconnect();
}
/*
Message-id format:
<file-path-relative-to-dataDir?xxx=yyy:zzz!1,2,3>
where:
xxx - part number (integer)
xxx - offset from which to read the files (integer)
yyy - size of file block to return (integer)
1,2,3 - list of server ids, which have the article (optional),
if the list is given and current server is not in the list
the "article not found"-error is returned.
Examples:
<parchecker/testfile.dat?1=0:50000> - return first 50000 bytes starting from beginning
<parchecker/testfile.dat?2=50000:50000> - return 50000 bytes starting from offset 50000
<parchecker/testfile.dat?2=50000:50000!2> - article is missing on server 1
*/
void NntpProcessor::ServArticle()
{
detail("[%i] Serving: %s", m_id, m_messageid);
bool ok = false;
const char* from = strchr(m_messageid, '?');
const char* off = strchr(m_messageid, '=');
const char* to = strchr(m_messageid, ':');
const char* end = strchr(m_messageid, '>');
const char* serv = strchr(m_messageid, '!');
if (from && off && to && end)
{
m_filename.Set(m_messageid + 1, from - m_messageid - 1);
m_part = atoi(from + 1);
m_offset = atoll(off + 1);
m_size = atoi(to + 1);
ok = !serv || ServerInList(serv + 1);
if (ok)
{
SendSegment();
return;
}
if (!ok)
{
m_connection->WriteLine("430 No Such Article Found\r\n");
}
}
else
{
m_connection->WriteLine("430 No Such Article Found (invalid message id format)\r\n");
}
}
bool NntpProcessor::ServerInList(const char* servList)
{
Tokenizer tok(servList, ",");
while (const char* servid = tok.Next())
{
if (atoi(servid) == m_serverId)
{
return true;
}
}
return false;
}
void NntpProcessor::SendSegment()
{
detail("[%i] Sending segment %s (%i=%lli:%i)", m_id, *m_filename, m_part, (long long)m_offset, m_size);
BString<1024> fullFilename("%s/%s", m_dataDir, *m_filename);
BString<1024> cacheFileDir("%s/%s", m_cacheDir, *m_filename);
BString<1024> cacheFileName("%i=%lli-%i", m_part, (long long)m_offset, m_size);
BString<1024> cacheFullFilename("%s/%s", *cacheFileDir, *cacheFileName);
DiskFile cacheFile;
bool readCache = m_cacheDir && cacheFile.Open(cacheFullFilename, DiskFile::omRead);
bool writeCache = m_cacheDir && !readCache;
CString errmsg;
if (writeCache && !FileSystem::ForceDirectories(cacheFileDir, errmsg))
{
error("Could not create directory %s: %s", *cacheFileDir, *errmsg);
}
if (writeCache && !cacheFile.Open(cacheFullFilename, DiskFile::omWrite))
{
error("Could not create file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
}
if (!readCache && !FileSystem::FileExists(fullFilename))
{
m_connection->WriteLine(CString::FormatStr("430 Article not found\r\n"));
return;
}
YEncoder encoder(fullFilename, m_part, m_offset, m_size,
[con = m_connection.get(), writeCache, &cacheFile](const char* buf, int size)
{
if (writeCache)
{
cacheFile.Write(buf, size);
}
con->Send(buf, size);
});
if (!readCache && !encoder.OpenFile(errmsg))
{
m_connection->WriteLine(CString::FormatStr("403 %s\r\n", *errmsg));
return;
}
m_connection->WriteLine(CString::FormatStr("%i, 0 %s\r\n", m_sendHeaders ? 222 : 220, m_messageid));
if (m_sendHeaders)
{
m_connection->WriteLine(CString::FormatStr("Message-ID: %s\r\n", m_messageid));
m_connection->WriteLine(CString::FormatStr("Subject: \"%s\"\r\n", FileSystem::BaseFileName(m_filename)));
m_connection->WriteLine("\r\n");
}
if (readCache)
{
cacheFile.Seek(0, DiskFile::soEnd);
int size = (int)cacheFile.Position();
CharBuffer buf(size);
cacheFile.Seek(0);
if (cacheFile.Read((char*)buf, size) != size)
{
error("Could not read file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
}
m_connection->Send(buf, size);
}
else
{
encoder.WriteSegment();
}
m_connection->WriteLine(".\r\n");
}

48
daemon/nserv/NntpServer.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NNTPSERVER_H
#define NNTPSERVER_H
#include "Thread.h"
#include "Connection.h"
class NntpServer : public Thread
{
public:
NntpServer(int id, const char* host, int port, const char* secureCert,
const char* secureKey, const char* dataDir, const char* cacheDir) :
m_id(id), m_host(host), m_port(port), m_secureCert(secureCert),
m_secureKey(secureKey), m_dataDir(dataDir), m_cacheDir(cacheDir) {}
virtual void Run();
virtual void Stop();
private:
int m_id;
CString m_host;
int m_port;
CString m_dataDir;
CString m_cacheDir;
CString m_secureCert;
CString m_secureKey;
std::unique_ptr<Connection> m_connection;
};
#endif

View File

@@ -0,0 +1,131 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "NzbGenerator.h"
#include "Util.h"
#include "FileSystem.h"
#include "Log.h"
void NzbGenerator::Execute()
{
info("Generating nzbs for %s", *m_dataDir);
DirBrowser dir(m_dataDir);
while (const char* filename = dir.Next())
{
BString<1024> fullFilename("%s%c%s", *m_dataDir, PATH_SEPARATOR, filename);
int len = strlen(filename);
if (len > 4 && !strcasecmp(filename + len - 4, ".nzb"))
{
// skip nzb-files
continue;
}
GenerateNzb(fullFilename);
}
info("Nzb generation finished");
}
void NzbGenerator::GenerateNzb(const char* path)
{
BString<1024> nzbFilename("%s%c%s.nzb", *m_dataDir, PATH_SEPARATOR, FileSystem::BaseFileName(path));
if (FileSystem::FileExists(nzbFilename))
{
return;
}
info("Generating nzb for %s", FileSystem::BaseFileName(path));
DiskFile outfile;
if (!outfile.Open(nzbFilename, DiskFile::omWrite))
{
error("Could not create file %s", *nzbFilename);
return;
}
outfile.Print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
outfile.Print("<!DOCTYPE nzb PUBLIC \"-//newzBin//DTD NZB 1.0//EN\" \"http://www.newzbin.com/DTD/nzb/nzb-1.0.dtd\">\n");
outfile.Print("<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\n");
bool isDir = FileSystem::DirectoryExists(path);
if (isDir)
{
AppendDir(outfile, path);
}
else
{
AppendFile(outfile, path, nullptr);
}
outfile.Print("</nzb>\n");
outfile.Close();
}
void NzbGenerator::AppendDir(DiskFile& outfile, const char* path)
{
DirBrowser dir(path);
while (const char* filename = dir.Next())
{
BString<1024> fullFilename("%s%c%s", path, PATH_SEPARATOR, filename);
bool isDir = FileSystem::DirectoryExists(fullFilename);
if (!isDir)
{
AppendFile(outfile, fullFilename, FileSystem::BaseFileName(path));
}
}
}
void NzbGenerator::AppendFile(DiskFile& outfile, const char* filename, const char* relativePath)
{
detail("Processing %s", FileSystem::BaseFileName(filename));
int64 fileSize = FileSystem::FileSize(filename);
time_t timestamp = Util::CurrentTime();
int segmentCount = (int)((fileSize + m_segmentSize - 1) / m_segmentSize);
outfile.Print("<file poster=\"nserv\" date=\"%i\" subject=\"&quot;%s&quot; yEnc (1/%i)\">\n",
(int)timestamp, FileSystem::BaseFileName(filename), segmentCount);
outfile.Print("<groups>\n");
outfile.Print("<group>alt.binaries.test</group>\n");
outfile.Print("</groups>\n");
outfile.Print("<segments>\n");
int64 segOffset = 0;
for (int segno = 1; segno <= segmentCount; segno++)
{
int segSize = (int)(segOffset + m_segmentSize < fileSize ? m_segmentSize : fileSize - segOffset);
outfile.Print("<segment bytes=\"%i\" number=\"%i\">%s%s%s?%i=%lli:%i</segment>\n",
m_segmentSize, segno,
relativePath ? relativePath : "",
relativePath ? "/" : "",
FileSystem::BaseFileName(filename), segno, (long long)segOffset, (int)segSize);
segOffset += segSize;
}
outfile.Print("</segments>\n");
outfile.Print("</file>\n");
}

View File

@@ -0,0 +1,43 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NZBGENERATOR_H
#define NZBGENERATOR_H
#include "NString.h"
#include "FileSystem.h"
class NzbGenerator
{
public:
NzbGenerator(const char* dataDir, int segmentSize) :
m_dataDir(dataDir), m_segmentSize(segmentSize) {};
void Execute();
private:
CString m_dataDir;
int m_segmentSize;
void GenerateNzb(const char* path);
void AppendFile(DiskFile& outfile, const char* filename, const char* relativePath);
void AppendDir(DiskFile& outfile, const char* path);
};
#endif

131
daemon/nserv/YEncoder.cpp Normal file
View File

@@ -0,0 +1,131 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "YEncoder.h"
#include "Util.h"
#include "FileSystem.h"
#include "Log.h"
bool YEncoder::OpenFile(CString& errmsg)
{
if (m_size < 0)
{
errmsg = "Invalid segment size";
return false;
}
if (!m_diskfile.Open(m_filename, DiskFile::omRead) || !m_diskfile.Seek(0, DiskFile::soEnd))
{
errmsg = "File not found";
return false;
}
m_fileSize = m_diskfile.Position();
if (m_size == 0)
{
m_size = (int)(m_fileSize - m_offset + 1);
}
if (m_fileSize < m_offset + m_size)
{
errmsg = "Invalid segment size";
return false;
}
if (!m_diskfile.Seek(m_offset))
{
errmsg = "Invalid segment offset";
return false;
}
return true;
}
void YEncoder::WriteSegment()
{
StringBuilder outbuf;
outbuf.Reserve(std::max(2048, std::min((int)(m_size * 1.1), 16 * 1024 * 1024)));
outbuf.Append(CString::FormatStr("=ybegin part=%i line=128 size=%lli name=%s\r\n", m_part, (long long)m_fileSize, FileSystem::BaseFileName(m_filename)));
outbuf.Append(CString::FormatStr("=ypart begin=%lli end=%lli\r\n", (long long)(m_offset + 1), (long long)(m_offset + m_size)));
uint32 crc = 0xFFFFFFFF;
CharBuffer inbuf(std::min(m_size, 16 * 1024 * 1024));
int lnsz = 0;
char* out = (char*)outbuf + outbuf.Length();
while (m_diskfile.Position() < m_offset + m_size)
{
int64 needBytes = std::min((int64)inbuf.Size(), m_offset + m_size - m_diskfile.Position());
int64 readBytes = m_diskfile.Read(inbuf, needBytes);
bool lastblock = m_diskfile.Position() == m_offset + m_size;
if (readBytes == 0)
{
return; // error;
}
crc = Util::Crc32m(crc, (uchar*)(const char*)inbuf, (int)readBytes);
char* in = inbuf;
while (readBytes > 0)
{
char ch = *in++;
readBytes--;
ch = (char)(((uchar)(ch) + 42) % 256);
if (ch == '\0' || ch == '\n' || ch == '\r' || ch == '=' || ch == ' ' || ch == '\t')
{
*out++ = '=';
lnsz++;
ch = (char)(((uchar)ch + 64) % 256);
}
if (ch == '.' && lnsz == 0)
{
*out++ = '.';
lnsz++;
}
*out++ = ch;
lnsz++;
if (lnsz >= 128 || (readBytes == 0 && lastblock))
{
*out++ = '\r';
*out++ = '\n';
lnsz += 2;
outbuf.SetLength(outbuf.Length() + lnsz);
if (outbuf.Length() > outbuf.Capacity() - 200)
{
m_writeFunc(outbuf, outbuf.Length());
outbuf.SetLength(0);
out = (char*)outbuf;
}
lnsz = 0;
}
}
}
crc ^= 0xFFFFFFFF;
m_diskfile.Close();
outbuf.Append(CString::FormatStr("=yend size=%i part=0 pcrc32=%08x\r\n", m_size, (unsigned int)crc));
m_writeFunc(outbuf, outbuf.Length());
}

47
daemon/nserv/YEncoder.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef YENCODER_H
#define YENCODER_H
#include "NString.h"
#include "FileSystem.h"
class YEncoder
{
public:
typedef std::function<void(const char* buf, int size)> WriteFunc;
YEncoder(const char* filename, int part, int64 offset, int size, WriteFunc writeFunc) :
m_filename(filename), m_part(part), m_offset(offset), m_size(size), m_writeFunc(writeFunc) {};
bool OpenFile(CString& errmsg);
void WriteSegment();
private:
DiskFile m_diskfile;
CString m_filename;
int m_part;
int64 m_offset;
int m_size;
int64 m_fileSize;
WriteFunc m_writeFunc;
};
#endif

View File

@@ -76,7 +76,6 @@ void MoveController::Run()
m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msFailure);
}
m_postInfo->SetStage(PostInfo::ptQueued);
m_postInfo->SetWorking(false);
}
@@ -182,7 +181,6 @@ void CleanupController::Run()
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csFailure);
}
m_postInfo->SetStage(PostInfo::ptQueued);
m_postInfo->SetWorking(false);
}

View File

@@ -0,0 +1,546 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "DirectUnpack.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
#include "Options.h"
void DirectUnpack::StartJob(NzbInfo* nzbInfo)
{
DirectUnpack* directUnpack = new DirectUnpack();
directUnpack->m_nzbId = nzbInfo->GetId();
directUnpack->SetAutoDestroy(true);
nzbInfo->SetUnpackThread(directUnpack);
nzbInfo->SetDirectUnpackStatus(NzbInfo::nsRunning);
directUnpack->Start();
}
void DirectUnpack::Run()
{
debug("Entering DirectUnpack-loop for %i", m_nzbId);
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (!nzbInfo)
{
debug("Could not find NzbInfo for %i", m_nzbId);
return;
}
m_name = nzbInfo->GetName();
m_destDir = nzbInfo->GetDestDir();
m_finalDir = nzbInfo->BuildFinalDirName();
NzbParameter* parameter = nzbInfo->GetParameters()->Find("*Unpack:Password", false);
if (parameter)
{
m_password = parameter->GetValue();
}
}
m_infoName.Format("direct unpack for %s", *m_name);
m_infoNameUp.Format("Direct unpack for %s", *m_name); // first letter in upper case
FindArchiveFiles();
SetInfoName(m_infoName);
SetWorkingDir(m_destDir);
while (!IsStopped())
{
CString archive;
{
Guard guard(m_volumeMutex);
if (!m_archives.empty())
{
archive = std::move(m_archives.front());
m_archives.pop_front();
}
}
if (archive)
{
if (!m_processed)
{
PrintMessage(Message::mkInfo, "Directly unpacking %s", *m_name);
Cleanup();
CreateUnpackDir();
m_processed = true;
}
ExecuteUnrar(archive);
if (!m_unpackOk)
{
break;
}
}
else
{
if (m_nzbCompleted)
{
break;
}
usleep(100 * 1000);
}
}
if (!m_unpackOk)
{
Cleanup();
}
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (!nzbInfo || nzbInfo->GetUnpackThread() != this)
{
debug("Could not find NzbInfo for %s", *m_infoName);
return;
}
nzbInfo->SetUnpackThread(nullptr);
nzbInfo->SetDirectUnpackStatus(m_processed ? m_unpackOk && !IsStopped() ? NzbInfo::nsSuccess : NzbInfo::nsFailure : NzbInfo::nsNone);
if (nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsSuccess)
{
nzbInfo->AddMessage(Message::mkInfo, BString<1024>("%s successful", *m_infoNameUp));
}
else if (nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsFailure)
{
nzbInfo->AddMessage(Message::mkWarning, BString<1024>("%s failed", *m_infoNameUp));
}
if (nzbInfo->GetPostInfo() && nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsSuccess)
{
std::move(m_extractedArchives.begin(), m_extractedArchives.end(),
std::back_inserter(*nzbInfo->GetPostInfo()->GetExtractedArchives()));
}
AddExtraTime(nzbInfo);
if (nzbInfo->GetPostInfo())
{
nzbInfo->GetPostInfo()->SetWorking(false);
}
}
debug("Exiting DirectUnpack-loop for %i", m_nzbId);
}
void DirectUnpack::ExecuteUnrar(const char* archiveName)
{
// Format:
// unrar x -y -p- -o+ -vp <archive.part0001.rar> <dest-dir>/_unpack/
ParamList params;
if (!PrepareCmdParams(g_Options->GetUnrarCmd(), &params, "unrar"))
{
return;
}
if (!params.Exists("x") && !params.Exists("e"))
{
params.emplace_back("x");
}
params.emplace_back("-y");
if (!m_password.Empty())
{
params.push_back(CString::FormatStr("-p%s", *m_password));
}
else
{
params.emplace_back("-p-");
}
if (!params.Exists("-o+") && !params.Exists("-o-"))
{
params.emplace_back("-o+");
}
params.emplace_back("-vp");
params.emplace_back(archiveName);
m_unpackExtendedDir = FileSystem::MakeExtendedPath(m_unpackDir, true);
params.push_back(*BString<1024>("%s%c", *m_unpackExtendedDir, PATH_SEPARATOR));
SetArgs(std::move(params));
SetLogPrefix("Unrar");
ResetEnv();
m_allOkMessageReceived = false;
SetNeedWrite(true);
m_unpacking = true;
int exitCode = Execute();
m_unpacking = false;
SetLogPrefix(nullptr);
m_unpackOk = exitCode == 0 && m_allOkMessageReceived && !GetTerminated();
if (!m_unpackOk && exitCode > 0)
{
PrintMessage(Message::mkError, "Unrar error code: %i", exitCode);
}
}
bool DirectUnpack::PrepareCmdParams(const char* command, ParamList* params, const char* infoName)
{
if (FileSystem::FileExists(command))
{
params->emplace_back(command);
return true;
}
std::vector<CString> cmdArgs = Util::SplitCommandLine(command);
if (cmdArgs.empty())
{
PrintMessage(Message::mkError, "Could not start %s, failed to parse command line: %s", infoName, command);
m_unpackOk = false;
return false;
}
std::move(cmdArgs.begin(), cmdArgs.end(), std::back_inserter(*params));
return true;
}
void DirectUnpack::CreateUnpackDir()
{
bool useInterDir = !Util::EmptyStr(g_Options->GetInterDir()) &&
!strncmp(m_destDir, g_Options->GetInterDir(), strlen(g_Options->GetInterDir())) &&
m_destDir[strlen(g_Options->GetInterDir())] == PATH_SEPARATOR;
m_finalDirCreated = useInterDir && !FileSystem::DirectoryExists(m_finalDir);
const char* destDir = useInterDir && !m_finalDir.Empty() ? *m_finalDir : *m_destDir;
m_unpackDir.Format("%s%c%s", destDir, PATH_SEPARATOR, "_unpack");
detail("Unpacking into %s", *m_unpackDir);
CString errmsg;
if (!FileSystem::ForceDirectories(m_unpackDir, errmsg))
{
PrintMessage(Message::mkError, "Could not create directory %s: %s", *m_unpackDir, *errmsg);
}
}
void DirectUnpack::FindArchiveFiles()
{
Guard guard(m_volumeMutex);
DirBrowser dir(m_destDir);
while (const char* filename = dir.Next())
{
if (IsMainArchive(filename))
{
BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, filename);
if (!FileSystem::DirectoryExists(fullFilename))
{
m_archives.emplace_back(filename);
}
}
}
}
bool DirectUnpack::IsMainArchive(const char* filename)
{
RegEx regExRarPart(".*\\.part([0-9]+)\\.rar$");
bool mainPart = Util::EndsWith(filename, ".rar", false) &&
(!regExRarPart.Match(filename) || atoi(filename + regExRarPart.GetMatchStart(1)) == 1);
return mainPart;
}
/**
* Unrar prints "Insert disk"-message without new line terminator.
* In order to become the message we analyze the output after every char.
*/
bool DirectUnpack::ReadLine(char* buf, int bufSize, FILE* stream)
{
int i = 0;
bool backspace = false;
for (; i < bufSize - 1; i++)
{
int ch = fgetc(stream);
if (ch == '\b')
{
backspace = true;
}
if (!backspace)
{
buf[i] = ch;
buf[i+1] = '\0';
}
if (ch == EOF)
{
break;
}
if (ch == '\n')
{
i++;
break;
}
if (i > 35 && ch == ' ' &&
!strncmp(buf, "Insert disk with", 16) && strstr(buf, " [C]ontinue, [Q]uit "))
{
i++;
break;
}
}
// skip unnecessary progress messages
if (!strncmp(buf, "...", 3))
{
buf[0] = '\0';
}
buf[i] = '\0';
return i > 0;
}
void DirectUnpack::AddMessage(Message::EKind kind, const char* text)
{
debug("%s", text);
BString<1024> msgText = text;
int len = strlen(text);
// Modify unrar messages for better readability:
// remove the destination path part from message "Extracting file.xxx"
if (!strncmp(text, "Unrar: Extracting ", 19) &&
!strncmp(text + 19, m_unpackExtendedDir, strlen(m_unpackExtendedDir)))
{
msgText.Format("Unrar: Extracting %s", text + 19 + strlen(m_unpackExtendedDir) + 1);
}
if (!strncmp(text, "Unrar: Insert disk with", 23) && strstr(text, " [C]ontinue, [Q]uit"))
{
BString<1024> filename;
filename.Set(text + 24, strstr(text, " [C]ontinue, [Q]uit") - text - 24);
WaitNextVolume(filename);
return;
}
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (nzbInfo)
{
nzbInfo->AddMessage(kind, msgText);
}
else
{
ScriptController::AddMessage(kind, msgText);
}
if (!strncmp(msgText, "Unrar: Extracting ", 18) && nzbInfo)
{
SetProgressLabel(nzbInfo, msgText + 7);
}
if (!strncmp(text, "Unrar: Extracting from ", 23) && nzbInfo)
{
SetProgressLabel(nzbInfo, text + 7);
m_extractedArchives.emplace_back(text + 23);
}
if (!IsStopped() && (
!strncmp(text, "Unrar: Checksum error in the encrypted file", 42) ||
!strncmp(text, "Unrar: CRC failed in the encrypted file", 39) ||
!strncmp(text, "Unrar: The specified password is incorrect.", 43) ||
strstr(text, " : packed data CRC failed in volume") ||
strstr(text, " : packed data checksum error in volume") ||
(len > 13 && !strncmp(text + len - 13, " - CRC failed", 13)) ||
(len > 18 && !strncmp(text + len - 18, " - checksum failed", 18)) ||
!strncmp(text, "Unrar: WARNING: You need to start extraction from a previous volume", 67)))
{
Stop(downloadQueue, nzbInfo);
}
if (!strncmp(text, "Unrar: All OK", 13))
{
m_allOkMessageReceived = true;
}
}
void DirectUnpack::Stop(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
debug("Stopping direct unpack for %s", *m_infoName);
if (m_processed)
{
if (nzbInfo)
{
nzbInfo->AddMessage(Message::mkWarning, BString<1024>("Cancelling %s", *m_infoName));
}
else
{
warn("Cancelling %s", *m_infoName);
}
}
AddExtraTime(nzbInfo);
if (nzbInfo->GetPostInfo())
{
nzbInfo->GetPostInfo()->SetWorking(false);
}
Thread::Stop();
if (m_unpacking)
{
Terminate();
}
}
void DirectUnpack::WaitNextVolume(const char* filename)
{
debug("WaitNextVolume for %s", filename);
BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, filename);
if (FileSystem::FileExists(fullFilename))
{
Write("\n"); // emulating click on Enter-key for "continue"
}
else
{
Guard guard(m_volumeMutex);
m_waitingFile = filename;
if (m_nzbCompleted)
{
// nzb completed but unrar waits for another volume
PrintMessage(Message::mkWarning, "Could not find volume %s", filename);
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
Stop(downloadQueue, nzbInfo);
}
}
}
void DirectUnpack::FileDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo)
{
debug("FileDownloaded for %s/%s", fileInfo->GetNzbInfo()->GetName(), fileInfo->GetFilename());
if (fileInfo->GetNzbInfo()->GetFailedArticles() > 0)
{
Stop(downloadQueue, fileInfo->GetNzbInfo());
return;
}
Guard guard(m_volumeMutex);
if (m_waitingFile && !strcasecmp(fileInfo->GetFilename(), m_waitingFile))
{
m_waitingFile = nullptr;
Write("\n"); // emulating click on Enter-key for "continue"
}
if (IsMainArchive(fileInfo->GetFilename()))
{
m_archives.emplace_back(fileInfo->GetFilename());
}
}
void DirectUnpack::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
debug("NzbDownloaded for %s", nzbInfo->GetName());
Guard guard(m_volumeMutex);
m_nzbCompleted = true;
if (m_waitingFile)
{
// nzb completed but unrar waits for another volume
nzbInfo->AddMessage(Message::mkWarning, BString<1024>("Unrar: Could not find volume %s", *m_waitingFile));
Stop(downloadQueue, nzbInfo);
return;
}
m_extraStartTime = Util::CurrentTime();
if (nzbInfo->GetPostInfo())
{
nzbInfo->GetPostInfo()->SetProgressLabel(m_progressLabel);
}
}
void DirectUnpack::NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
debug("NzbDeleted for %s", nzbInfo->GetName());
nzbInfo->SetUnpackThread(nullptr);
nzbInfo->SetDirectUnpackStatus(NzbInfo::nsFailure);
Stop(downloadQueue, nzbInfo);
}
// Remove _unpack-dir
void DirectUnpack::Cleanup()
{
debug("Cleanup for %s", *m_infoName);
CString errmsg;
if (FileSystem::DirectoryExists(m_unpackDir) &&
!FileSystem::DeleteDirectoryWithContent(m_unpackDir, errmsg))
{
PrintMessage(Message::mkError, "Could not delete temporary directory %s: %s", *m_unpackDir, *errmsg);
}
if (m_finalDirCreated)
{
FileSystem::DeleteDirectory(m_finalDir);
}
}
void DirectUnpack::SetProgressLabel(NzbInfo* nzbInfo, const char* progressLabel)
{
m_progressLabel = progressLabel;
if (nzbInfo->GetPostInfo())
{
nzbInfo->GetPostInfo()->SetProgressLabel(progressLabel);
}
}
void DirectUnpack::AddExtraTime(NzbInfo* nzbInfo)
{
if (m_extraStartTime)
{
time_t extraTime = Util::CurrentTime() - m_extraStartTime;
nzbInfo->SetUnpackSec(nzbInfo->GetUnpackSec() + extraTime);
nzbInfo->SetPostTotalSec(nzbInfo->GetPostTotalSec() + extraTime);
m_extraStartTime = 0;
}
}
bool DirectUnpack::IsArchiveFilename(const char* filename)
{
if (Util::EndsWith(filename, ".rar", false))
{
return true;
}
RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
return regExRarMultiSeq.Match(filename);
}

View File

@@ -0,0 +1,86 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DIRECTUNPACK_H
#define DIRECTUNPACK_H
#include "Log.h"
#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
class DirectUnpack : public Thread, public ScriptController
{
public:
virtual void Run();
static void StartJob(NzbInfo* nzbInfo);
void Stop(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void FileDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo);
void NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
static bool IsArchiveFilename(const char* filename);
protected:
virtual bool ReadLine(char* buf, int bufSize, FILE* stream);
virtual void AddMessage(Message::EKind kind, const char* text);
private:
class ParamList : public std::vector<CString>
{
public:
bool Exists(const char* param) { return std::find(begin(), end(), param) != end(); }
};
typedef std::deque<CString> ArchiveList;
int m_nzbId;
CString m_name;
CString m_infoName;
CString m_infoNameUp;
CString m_destDir;
CString m_finalDir;
CString m_unpackDir;
CString m_unpackExtendedDir;
CString m_password;
CString m_waitingFile;
CString m_progressLabel;
bool m_allOkMessageReceived = false;
bool m_unpackOk = false;
bool m_finalDirCreated = false;
bool m_nzbCompleted = false;
Mutex m_volumeMutex;
ArchiveList m_archives;
bool m_processed = false;
bool m_unpacking = false;
time_t m_extraStartTime = 0;
ArchiveList m_extractedArchives;
void CreateUnpackDir();
void FindArchiveFiles();
void ExecuteUnrar(const char* archiveName);
bool PrepareCmdParams(const char* command, ParamList* params, const char* infoName);
void WaitNextVolume(const char* filename);
void Cleanup();
bool IsMainArchive(const char* filename);
void SetProgressLabel(NzbInfo* nzbInfo, const char* progressLabel);
void AddExtraTime(NzbInfo* nzbInfo);
};
#endif

View File

@@ -55,7 +55,9 @@ class RepairThread;
class Repairer : public Par2::Par2Repairer, public ParChecker::AbstractRepairer
{
public:
Repairer(ParChecker* owner) { m_owner = owner; }
Repairer(ParChecker* owner):
Par2::Par2Repairer(owner->m_parCout, owner->m_parCerr),
m_owner(owner), commandLine(owner->m_parCout, owner->m_parCerr) {}
Par2::Result PreProcess(const char *parFilename);
Par2::Result Process(bool dorepair);
virtual Repairer* GetRepairer() { return this; }
@@ -364,13 +366,6 @@ int ParChecker::StreamBuf::overflow(int ch)
}
ParChecker::~ParChecker()
{
debug("Destroying ParChecker");
Cleanup();
}
void ParChecker::Cleanup()
{
m_repairer.reset();
@@ -381,14 +376,11 @@ void ParChecker::Cleanup()
m_errMsg = nullptr;
}
void ParChecker::Run()
void ParChecker::Execute()
{
Par2::cout.rdbuf(&m_parOutStream);
Par2::cerr.rdbuf(&m_parErrStream);
m_status = RunParCheckAll();
if (m_status == psRepairNotNeeded && m_parQuick && m_forceRepair && !m_cancelled)
if (m_status == psRepairNotNeeded && m_parQuick && m_forceRepair && !IsStopped())
{
PrintMessage(Message::mkInfo, "Performing full par-check for %s", *m_nzbName);
m_parQuick = false;
@@ -396,9 +388,6 @@ void ParChecker::Run()
}
Completed();
Par2::cout.rdbuf(&Par2::nullStreamBuf);
Par2::cerr.rdbuf(&Par2::nullStreamBuf);
}
ParChecker::EStatus ParChecker::RunParCheckAll()
@@ -411,23 +400,22 @@ ParChecker::EStatus ParChecker::RunParCheckAll()
}
EStatus allStatus = psRepairNotNeeded;
m_cancelled = false;
m_parFull = true;
for (CString& parFilename : fileList)
{
debug("Found par: %s", *parFilename);
if (!IsStopped() && !m_cancelled)
if (!IsStopped())
{
BString<1024> fullParFilename( "%s%c%s", *m_destDir, (int)PATH_SEPARATOR, *parFilename);
BString<1024> fullParFilename( "%s%c%s", *m_destDir, PATH_SEPARATOR, *parFilename);
int baseLen = 0;
ParParser::ParseParFilename(parFilename, &baseLen, nullptr);
ParParser::ParseParFilename(parFilename, true, &baseLen, nullptr);
BString<1024> infoName;
infoName.Set(parFilename, baseLen);
BString<1024> parInfoName("%s%c%s", *m_nzbName, (int)PATH_SEPARATOR, *infoName);
BString<1024> parInfoName("%s%c%s", *m_nzbName, PATH_SEPARATOR, *infoName);
SetInfoName(parInfoName);
EStatus status = RunParCheck(fullParFilename);
@@ -571,7 +559,7 @@ ParChecker::EStatus ParChecker::RunParCheck(const char* parFilename)
}
}
if (m_cancelled)
if (IsStopped())
{
if (m_stage >= ptRepairing)
{
@@ -688,7 +676,7 @@ bool ParChecker::LoadMainParBak()
{
// wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged"
bool queuedParFilesChanged = false;
while (!queuedParFilesChanged && !IsStopped() && !m_cancelled)
while (!queuedParFilesChanged && !IsStopped())
{
{
Guard guard(m_queuedParFilesMutex);
@@ -754,7 +742,7 @@ int ParChecker::ProcessMorePars()
{
// wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged"
bool queuedParFilesChanged = false;
while (!queuedParFilesChanged && !IsStopped() && !m_cancelled)
while (!queuedParFilesChanged && !IsStopped())
{
{
Guard guard(m_queuedParFilesMutex);
@@ -765,7 +753,7 @@ int ParChecker::ProcessMorePars()
}
}
if (IsStopped() || m_cancelled)
if (IsStopped())
{
break;
}
@@ -973,7 +961,7 @@ bool ParChecker::AddExtraFiles(bool onlyMissing, bool externalDir, const char* d
// adding files one by one until all missing files are found
while (!IsStopped() && !m_cancelled && extrafiles.size() > 0)
while (!IsStopped() && extrafiles.size() > 0)
{
std::list<Par2::CommandLine::ExtraFile> extrafiles1;
extrafiles1.splice(extrafiles1.end(), extrafiles, extrafiles.begin());
@@ -1199,13 +1187,12 @@ void ParChecker::CheckEmptyFiles()
void ParChecker::Cancel()
{
GetRepairer()->cancelled = true;
m_cancelled = true;
QueueChanged();
}
void ParChecker::WriteBrokenLog(EStatus status)
{
BString<1024> brokenLogName("%s%c_brokenlog.txt", *m_destDir, (int)PATH_SEPARATOR);
BString<1024> brokenLogName("%s%c_brokenlog.txt", *m_destDir, PATH_SEPARATOR);
if (status != psRepairNotNeeded || FileSystem::FileExists(brokenLogName))
{
@@ -1214,7 +1201,7 @@ void ParChecker::WriteBrokenLog(EStatus status)
{
if (status == psFailed)
{
if (m_cancelled)
if (IsStopped())
{
file.Print("Repair cancelled for %s\n", *m_infoName);
}

View File

@@ -25,13 +25,12 @@
#include "NString.h"
#include "Container.h"
#include "Thread.h"
#include "FileSystem.h"
#include "Log.h"
class Repairer;
class ParChecker : public Thread
class ParChecker
{
public:
enum EStatus
@@ -57,8 +56,7 @@ public:
virtual Repairer* GetRepairer() = 0;
};
virtual ~ParChecker();
virtual void Run();
void Execute();
void SetDestDir(const char* destDir) { m_destDir = destDir; }
const char* GetParFilename() { return m_parFilename; }
const char* GetInfoName() { return m_infoName; }
@@ -74,7 +72,6 @@ public:
void AddParFile(const char* parFilename);
void QueueChanged();
void Cancel();
bool GetCancelled() { return m_cancelled; }
protected:
class Segment
@@ -129,6 +126,7 @@ protected:
*/
virtual bool RequestMorePars(int blockNeeded, int* blockFound) = 0;
virtual void UpdateProgress() {}
virtual bool IsStopped() { return false; };
virtual void Completed() {}
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
virtual void RegisterParredFile(const char* filename) {}
@@ -186,6 +184,8 @@ private:
DupeSourceList m_dupeSources;
StreamBuf m_parOutStream{this, Message::mkDetail};
StreamBuf m_parErrStream{this, Message::mkError};
std::ostream m_parCout{&m_parOutStream};
std::ostream m_parCerr{&m_parErrStream};
// "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.

View File

@@ -33,7 +33,7 @@ bool ParParser::FindMainPars(const char* path, ParFileList* fileList)
while (const char* filename = dir.Next())
{
int baseLen = 0;
if (ParseParFilename(filename, &baseLen, nullptr))
if (ParseParFilename(filename, true, &baseLen, nullptr))
{
if (!fileList)
{
@@ -44,7 +44,7 @@ bool ParParser::FindMainPars(const char* path, ParFileList* fileList)
bool exists = false;
for (CString& filename2 : fileList)
{
exists = SameParCollection(filename, filename2);
exists = SameParCollection(filename, filename2, true);
if (exists)
{
break;
@@ -59,16 +59,16 @@ bool ParParser::FindMainPars(const char* path, ParFileList* fileList)
return fileList && !fileList->empty();
}
bool ParParser::SameParCollection(const char* filename1, const char* filename2)
bool ParParser::SameParCollection(const char* filename1, const char* filename2, bool confirmedFilenames)
{
int baseLen1 = 0, baseLen2 = 0;
return ParseParFilename(filename1, &baseLen1, nullptr) &&
ParseParFilename(filename2, &baseLen2, nullptr) &&
return ParseParFilename(filename1, confirmedFilenames, &baseLen1, nullptr) &&
ParseParFilename(filename2, confirmedFilenames, &baseLen2, nullptr) &&
baseLen1 == baseLen2 &&
!strncasecmp(filename1, filename2, baseLen1);
}
bool ParParser::ParseParFilename(const char* parFilename, int* baseNameLen, int* blocks)
bool ParParser::ParseParFilename(const char* parFilename, bool confirmedFilename, int* baseNameLen, int* blocks)
{
BString<1024> filename = parFilename;
for (char* p = filename; *p; p++) *p = tolower(*p); // convert string to lowercase
@@ -79,10 +79,13 @@ bool ParParser::ParseParFilename(const char* parFilename, int* baseNameLen, int*
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';
if (!confirmedFilename)
{
// 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)

View File

@@ -30,8 +30,8 @@ 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);
static bool ParseParFilename(const char* parFilename, bool confirmedFilename, int* baseNameLen, int* blocks);
static bool SameParCollection(const char* filename1, const char* filename2, bool confirmedFilenames);
};
#endif

View File

@@ -36,31 +36,17 @@
class ParRenamerRepairer : public Par2::Par2Repairer
{
public:
ParRenamerRepairer() : Par2::Par2Repairer(m_nout, m_nout) {};
friend class ParRenamer;
private:
class NullStreamBuf : public std::streambuf {};
NullStreamBuf m_nullbuf;
std::ostream m_nout{&m_nullbuf};
};
void ParRenamer::Cleanup()
void ParRenamer::Execute()
{
m_dirList.clear();
m_fileHashList.clear();
}
void ParRenamer::Cancel()
{
m_cancelled = true;
}
void ParRenamer::Run()
{
Cleanup();
m_cancelled = false;
m_fileCount = 0;
m_curFile = 0;
m_renamedCount = 0;
m_hasMissedFiles = false;
m_status = psFailed;
m_progressLabel.Format("Checking renamed files for %s", *m_infoName);
m_stageProgress = 0;
UpdateProgress();
@@ -71,14 +57,17 @@ void ParRenamer::Run()
{
debug("Checking %s", *destDir);
m_fileHashList.clear();
LoadParFiles(destDir);
m_parInfoList.clear();
m_badParList.clear();
m_loadedParList.clear();
if (m_fileHashList.empty())
CheckFiles(destDir, true);
RenameParFiles(destDir);
LoadMainParFiles(destDir);
if (m_hasDamagedParFiles)
{
int savedCurFile = m_curFile;
CheckFiles(destDir, true);
m_curFile = savedCurFile; // restore progress indicator
LoadParFiles(destDir);
LoadExtraParFiles(destDir);
}
CheckFiles(destDir, false);
@@ -87,24 +76,12 @@ void ParRenamer::Run()
{
CheckMissing();
}
}
if (m_cancelled)
{
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", *m_infoName);
if (m_renamedCount > 0 && !m_badParList.empty())
{
RenameBadParFiles();
}
}
else if (m_renamedCount > 0)
{
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_infoName);
}
Cleanup();
Completed();
}
void ParRenamer::BuildDirList(const char* destDir)
@@ -115,7 +92,7 @@ void ParRenamer::BuildDirList(const char* destDir)
while (const char* filename = dirBrowser.Next())
{
if (!m_cancelled)
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (FileSystem::DirectoryExists(fullFilename))
@@ -130,7 +107,7 @@ void ParRenamer::BuildDirList(const char* destDir)
}
}
void ParRenamer::LoadParFiles(const char* destDir)
void ParRenamer::LoadMainParFiles(const char* destDir)
{
ParParser::ParFileList parFileList;
ParParser::FindMainPars(destDir, &parFileList);
@@ -142,19 +119,52 @@ void ParRenamer::LoadParFiles(const char* destDir)
}
}
void ParRenamer::LoadExtraParFiles(const char* destDir)
{
DirBrowser dir(destDir);
while (const char* filename = dir.Next())
{
BString<1024> fullParFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (ParParser::ParseParFilename(fullParFilename, true, nullptr, nullptr))
{
bool knownBadParFile = std::find_if(m_badParList.begin(), m_badParList.end(),
[&fullParFilename](CString& filename)
{
return !strcmp(filename, fullParFilename);
}) != m_badParList.end();
bool loadedParFile = std::find_if(m_loadedParList.begin(), m_loadedParList.end(),
[&fullParFilename](CString& filename)
{
return !strcmp(filename, fullParFilename);
}) != m_loadedParList.end();
if (!knownBadParFile && !loadedParFile)
{
LoadParFile(fullParFilename);
}
}
}
}
void ParRenamer::LoadParFile(const char* parFilename)
{
ParRenamerRepairer repairer;
if (!repairer.LoadPacketsFromFile(parFilename))
if (!repairer.LoadPacketsFromFile(parFilename) || FileSystem::FileSize(parFilename) == 0)
{
PrintMessage(Message::mkWarning, "Could not load par2-file %s", parFilename);
m_hasDamagedParFiles = true;
m_badParList.emplace_back(parFilename);
return;
}
m_loadedParList.emplace_back(parFilename);
PrintMessage(Message::mkInfo, "Loaded par2-file %s for par-rename", FileSystem::BaseFileName(parFilename));
for (std::pair<const Par2::MD5Hash, Par2::Par2RepairerSourceFile*>& entry : repairer.sourcefilemap)
{
if (m_cancelled)
if (IsStopped())
{
break;
}
@@ -162,32 +172,46 @@ void ParRenamer::LoadParFile(const char* parFilename)
Par2::Par2RepairerSourceFile* sourceFile = entry.second;
if (!sourceFile || !sourceFile->GetDescriptionPacket())
{
PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", parFilename);
PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", FileSystem::BaseFileName(parFilename));
m_badParList.emplace_back(parFilename);
m_hasDamagedParFiles = true;
continue;
}
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());
std::string hash = sourceFile->GetDescriptionPacket()->Hash16k().print();
bool exists = std::find_if(m_fileHashList.begin(), m_fileHashList.end(),
[&hash](FileHash& fileHash)
{
return !strcmp(fileHash.GetHash(), hash.c_str());
})
!= m_fileHashList.end();
if (!exists)
{
m_fileHashList.emplace_back(filename.c_str(), hash.c_str());
RegisterParredFile(filename.c_str());
}
}
}
void ParRenamer::CheckFiles(const char* destDir, bool renamePars)
void ParRenamer::CheckFiles(const char* destDir, bool checkPars)
{
DirBrowser dir(destDir);
while (const char* filename = dir.Next())
{
if (!m_cancelled)
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (!FileSystem::DirectoryExists(fullFilename))
{
m_progressLabel.Format("Checking file %s", filename);
m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount / 2 : 1000;
UpdateProgress();
m_curFile++;
if (renamePars)
if (checkPars)
{
CheckParFile(destDir, fullFilename);
}
@@ -289,22 +313,10 @@ void ParRenamer::CheckRegularFile(const char* destDir, const char* filename)
}
}
/*
* 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* destDir, const char* filename)
{
debug("Checking par2-header for %s", filename);
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;
}
DiskFile file;
if (!file.Open(filename, DiskFile::omRead))
{
@@ -337,13 +349,70 @@ void ParRenamer::CheckParFile(const char* destDir, const char* filename)
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", FileSystem::BaseFileName(filename), *setId);
debug("Storing: %s; setid: %s", FileSystem::BaseFileName(filename), *setId);
m_parInfoList.emplace_back(filename, setId);
}
void ParRenamer::RenameParFiles(const char* destDir)
{
if (NeedRenameParFiles())
{
for (ParInfo& parInfo : m_parInfoList)
{
RenameParFile(destDir, parInfo.GetFilename(), parInfo.GetSetId());
}
}
}
bool ParRenamer::NeedRenameParFiles()
{
for (ParInfoList::iterator it1 = m_parInfoList.begin(); it1 != m_parInfoList.end(); it1++)
{
ParInfo& parInfo1 = *it1;
const char* baseName1 = FileSystem::BaseFileName(parInfo1.GetFilename());
const char* extension = strrchr(baseName1, '.');
if (!extension || strcasecmp(extension, ".par2"))
{
// file doesn't have "par2" extension
return true;
}
int baseLen1;
ParParser::ParseParFilename(baseName1, true, &baseLen1, nullptr);
for (ParInfoList::iterator it2 = it1 + 1; it2 != m_parInfoList.end(); it2++)
{
ParInfo& parInfo2 = *it2;
if (!strcmp(parInfo1.GetSetId(), parInfo2.GetSetId()))
{
const char* baseName2 = FileSystem::BaseFileName(parInfo2.GetFilename());
int baseLen2;
ParParser::ParseParFilename(baseName2, true, &baseLen2, nullptr);
if (baseLen1 != baseLen2 || strncasecmp(baseName1, baseName2, baseLen1))
{
// same setid but different base file names
return true;
}
}
}
}
return false;
}
void ParRenamer::RenameParFile(const char* destDir, const char* filename, const char* setId)
{
debug("Renaming: %s; setid: %s", FileSystem::BaseFileName(filename), setId);
BString<1024> destFileName;
int num = 1;
while (num == 1 || FileSystem::FileExists(destFileName))
{
destFileName.Format("%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, *setId, num);
destFileName.Format("%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, setId, num);
num++;
}
@@ -366,4 +435,13 @@ void ParRenamer::RenameFile(const char* srcFilename, const char* destFileName)
RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
}
void ParRenamer::RenameBadParFiles()
{
for (CString& parFilename : m_badParList)
{
BString<1024> destFileName("%s.bad", *parFilename);
RenameFile(parFilename, destFileName);
}
}
#endif

View File

@@ -24,32 +24,23 @@
#ifndef DISABLE_PARCHECK
#include "NString.h"
#include "Thread.h"
#include "Log.h"
class ParRenamer : public Thread
class ParRenamer
{
public:
enum EStatus
{
psFailed,
psSuccess
};
virtual void Run();
void Execute();
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; }
int GetRenamedCount() { return m_renamedCount; }
bool HasMissedFiles() { return m_hasMissedFiles; }
bool HasDamagedParFiles() { return m_hasDamagedParFiles; }
void SetDetectMissing(bool detectMissing) { m_detectMissing = detectMissing; }
protected:
virtual void UpdateProgress() {}
virtual void Completed() {}
virtual bool IsStopped() { return false; };
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) {}
@@ -72,34 +63,52 @@ private:
bool m_fileExists = false;
};
class ParInfo
{
public:
ParInfo(const char* filename, const char* setId) :
m_filename(filename), m_setId(setId) {}
const char* GetFilename() { return m_filename; }
const char* GetSetId() { return m_setId; }
private:
CString m_filename;
CString m_setId;
};
typedef std::deque<FileHash> FileHashList;
typedef std::deque<CString> DirList;
typedef std::deque<ParInfo> ParInfoList;
typedef std::deque<CString> NameList;
CString m_infoName;
CString m_destDir;
EStatus m_status;
CString m_progressLabel;
int m_stageProgress;
bool m_cancelled;
DirList m_dirList;
int m_stageProgress = 0;
NameList m_dirList;
FileHashList m_fileHashList;
int m_fileCount;
int m_curFile;
int m_renamedCount;
bool m_hasMissedFiles;
ParInfoList m_parInfoList;
NameList m_badParList;
NameList m_loadedParList;
int m_fileCount = 0;
int m_curFile = 0;
int m_renamedCount = 0;
bool m_hasMissedFiles = false;
bool m_detectMissing = false;
bool m_hasDamagedParFiles = false;
void BuildDirList(const char* destDir);
void CheckDir(const char* destDir);
void LoadParFiles(const char* destDir);
void LoadMainParFiles(const char* destDir);
void LoadExtraParFiles(const char* destDir);
void LoadParFile(const char* parFilename);
void CheckFiles(const char* destDir, bool renamePars);
void CheckFiles(const char* destDir, bool checkPars);
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 RenameParFiles(const char* destDir);
void RenameParFile(const char* destDir, const char* filename, const char* setId);
bool NeedRenameParFiles();
void RenameFile(const char* srcFilename, const char* destFileName);
void Cleanup();
void RenameBadParFiles();
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,16 +29,18 @@
#include "FileSystem.h"
#include "Unpack.h"
#include "Cleanup.h"
#include "Rename.h"
#include "Repair.h"
#include "NzbFile.h"
#include "QueueScript.h"
#include "ParParser.h"
#include "DirectUnpack.h"
PrePostProcessor::PrePostProcessor()
{
debug("Creating PrePostProcessor");
m_downloadQueueObserver.m_owner = this;
DownloadQueue::Guard()->Attach(&m_downloadQueueObserver);
DownloadQueue::Guard()->Attach(this);
}
void PrePostProcessor::Run()
@@ -57,49 +59,144 @@ void PrePostProcessor::Run()
while (!IsStopped())
{
if (!g_Options->GetTempPausePostprocess())
if (!g_Options->GetTempPausePostprocess() && m_queuedJobs)
{
// check post-queue every 200 msec
CheckPostQueue();
}
Util::SetStandByMode(!m_curJob);
usleep(200 * 1000);
}
WaitJobs();
debug("Exiting PrePostProcessor-loop");
}
void PrePostProcessor::WaitJobs()
{
debug("PrePostProcessor: waiting for jobs to complete");
// wait 5 seconds until all post-processing jobs gracefully finish
time_t waitStart = Util::CurrentTime();
while (Util::CurrentTime() < waitStart + 5)
{
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (m_activeJobs.empty())
{
break;
}
}
CheckPostQueue();
usleep(200 * 1000);
}
// kill remaining post-processing jobs; not safe but we can't wait any longer
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* postJob : m_activeJobs)
{
if (postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread())
{
Thread* thread = postJob->GetPostInfo()->GetPostThread();
postJob->GetPostInfo()->SetPostThread(nullptr);
warn("Terminating active post-process job for %s", postJob->GetName());
thread->Kill();
delete thread;
}
}
}
// wait 5 seconds until direct unpack threads gracefully finish
waitStart = Util::CurrentTime();
while (Util::CurrentTime() < waitStart + 5)
{
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (std::find_if(downloadQueue->GetQueue()->begin(),
downloadQueue->GetQueue()->end(),
[](const std::unique_ptr<NzbInfo>& nzbInfo)
{
return nzbInfo->GetUnpackThread() != nullptr;
}) == downloadQueue->GetQueue()->end())
{
break;
}
}
usleep(200 * 1000);
}
// disconnect remaining direct unpack jobs
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
nzbInfo->SetUnpackThread(nullptr);
}
}
debug("PrePostProcessor: Jobs are completed");
}
void PrePostProcessor::Stop()
{
Thread::Stop();
GuardedDownloadQueue guard = DownloadQueue::Guard();
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
#ifndef DISABLE_PARCHECK
m_parCoordinator.Stop();
#endif
if (m_curJob && m_curJob->GetPostInfo() &&
(m_curJob->GetPostInfo()->GetStage() == PostInfo::ptUnpacking ||
m_curJob->GetPostInfo()->GetStage() == PostInfo::ptExecutingScript) &&
m_curJob->GetPostInfo()->GetPostThread())
for (NzbInfo* postJob : m_activeJobs)
{
Thread* postThread = m_curJob->GetPostInfo()->GetPostThread();
m_curJob->GetPostInfo()->SetPostThread(nullptr);
postThread->SetAutoDestroy(true);
postThread->Stop();
if (postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread())
{
postJob->GetPostInfo()->GetPostThread()->Stop();
}
}
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
if (nzbInfo->GetUnpackThread())
{
((DirectUnpack*)nzbInfo->GetUnpackThread())->Stop(downloadQueue, nzbInfo);
}
}
}
void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
/**
* Reset the state of items after reloading from disk and
* delete items which could not be resumed.
* Also count the number of post-jobs.
*/
void PrePostProcessor::SanitisePostQueue()
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
PostInfo* postInfo = nzbInfo->GetPostInfo();
if (postInfo)
{
m_queuedJobs++;
if (postInfo->GetStage() == PostInfo::ptExecutingScript ||
!FileSystem::DirectoryExists(nzbInfo->GetDestDir()))
{
postInfo->SetStage(PostInfo::ptFinished);
}
else
{
postInfo->SetStage(PostInfo::ptQueued);
}
postInfo->SetWorking(false);
}
}
}
void PrePostProcessor::DownloadQueueUpdate(void* aspect)
{
if (IsStopped())
{
return;
}
DownloadQueue::Aspect* queueAspect = (DownloadQueue::Aspect*)Aspect;
DownloadQueue::Aspect* queueAspect = (DownloadQueue::Aspect*)aspect;
if (queueAspect->action == DownloadQueue::eaNzbFound)
{
NzbFound(queueAspect->downloadQueue, queueAspect->nzbInfo);
@@ -108,6 +205,10 @@ void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
{
NzbAdded(queueAspect->downloadQueue, queueAspect->nzbInfo);
}
else if (queueAspect->action == DownloadQueue::eaNzbNamed)
{
g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeNzbNamed);
}
else if (queueAspect->action == DownloadQueue::eaNzbDeleted &&
queueAspect->nzbInfo->GetDeleting() &&
!queueAspect->nzbInfo->GetPostInfo() &&
@@ -122,15 +223,23 @@ void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
else if ((queueAspect->action == DownloadQueue::eaFileCompleted ||
queueAspect->action == DownloadQueue::eaFileDeleted))
{
if (queueAspect->action == DownloadQueue::eaFileCompleted && !queueAspect->nzbInfo->GetPostInfo())
if (queueAspect->action == DownloadQueue::eaFileCompleted)
{
g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeFileDownloaded);
FileDownloaded(queueAspect->downloadQueue, queueAspect->nzbInfo, queueAspect->fileInfo);
}
#ifndef DISABLE_PARCHECK
if (m_parCoordinator.AddPar(queueAspect->fileInfo, queueAspect->action == DownloadQueue::eaFileDeleted))
for (NzbInfo* postJob : m_activeJobs)
{
return;
if (postJob && queueAspect->fileInfo->GetNzbInfo() == postJob &&
postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread() &&
postJob->GetPostInfo()->GetStage() >= PostInfo::ptLoadingPars &&
postJob->GetPostInfo()->GetStage() <= PostInfo::ptVerifyingRepaired &&
((RepairController*)postJob->GetPostInfo()->GetPostThread())->AddPar(
queueAspect->fileInfo, queueAspect->action == DownloadQueue::eaFileDeleted))
{
return;
}
}
#endif
@@ -138,7 +247,7 @@ void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
queueAspect->fileInfo->GetDupeDeleted()) &&
queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsHealth &&
!queueAspect->nzbInfo->GetPostInfo() &&
IsNzbFileCompleted(queueAspect->nzbInfo, true))
queueAspect->nzbInfo->IsDownloadCompleted(true))
{
queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
"Collection %s completely downloaded", queueAspect->nzbInfo->GetName());
@@ -149,7 +258,7 @@ void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
(queueAspect->action == DownloadQueue::eaFileCompleted &&
queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() > NzbInfo::dsNone)) &&
!queueAspect->nzbInfo->GetPostInfo() &&
IsNzbFileCompleted(queueAspect->nzbInfo, false))
queueAspect->nzbInfo->IsDownloadCompleted(false))
{
queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
"Collection %s deleted from queue", queueAspect->nzbInfo->GetName());
@@ -170,7 +279,13 @@ void PrePostProcessor::NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
if (g_Options->GetParCheck() != Options::pcForce)
{
m_parCoordinator.PausePars(downloadQueue, nzbInfo);
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseExtraPars, nullptr);
}
if (g_Options->GetReorderFiles() && nzbInfo->GetDeleteStatus() == NzbInfo::dsNone)
{
nzbInfo->PrintMessage(Message::mkInfo, "Reordering files for %s", nzbInfo->GetName());
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupSortFiles, nullptr);
}
if (nzbInfo->GetDeleteStatus() == NzbInfo::dsDupe ||
@@ -199,7 +314,7 @@ void PrePostProcessor::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbI
nzbInfo->PrintMessage(Message::mkInfo, "Queueing %s for post-processing", nzbInfo->GetName());
nzbInfo->EnterPostProcess();
m_jobCount++;
m_queuedJobs++;
if (nzbInfo->GetParStatus() == NzbInfo::psNone &&
g_Options->GetParCheck() != Options::pcAlways &&
@@ -208,21 +323,33 @@ void PrePostProcessor::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbI
nzbInfo->SetParStatus(NzbInfo::psSkipped);
}
if (nzbInfo->GetRenameStatus() == NzbInfo::rsNone && !g_Options->GetParRename())
if (nzbInfo->GetUnpackThread())
{
nzbInfo->SetRenameStatus(NzbInfo::rsSkipped);
nzbInfo->GetPostInfo()->SetWorking(true);
m_activeJobs.push_back(nzbInfo);
((DirectUnpack*)nzbInfo->GetUnpackThread())->NzbDownloaded(downloadQueue, nzbInfo);
}
downloadQueue->Save();
}
else
{
if (nzbInfo->GetUnpackThread())
{
((DirectUnpack*)nzbInfo->GetUnpackThread())->NzbDownloaded(downloadQueue, nzbInfo);
}
NzbCompleted(downloadQueue, nzbInfo, true);
}
}
void PrePostProcessor::NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
if (nzbInfo->GetUnpackThread())
{
((DirectUnpack*)nzbInfo->GetUnpackThread())->NzbDeleted(downloadQueue, nzbInfo);
}
if (nzbInfo->GetDeleteStatus() == NzbInfo::dsNone)
{
nzbInfo->SetDeleteStatus(NzbInfo::dsManual);
@@ -291,10 +418,10 @@ void PrePostProcessor::DeleteCleanup(NzbInfo* nzbInfo)
// download was cancelled, deleting already downloaded files from disk
for (CompletedFile& completedFile: nzbInfo->GetCompletedFiles())
{
BString<1024> fullFileName("%s%c%s", nzbInfo->GetDestDir(), (int)PATH_SEPARATOR, completedFile.GetFileName());
BString<1024> fullFileName("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, completedFile.GetFilename());
if (FileSystem::FileExists(fullFileName))
{
detail("Deleting file %s", completedFile.GetFileName());
detail("Deleting file %s", completedFile.GetFilename());
FileSystem::DeleteFile(fullFileName);
}
}
@@ -313,97 +440,144 @@ void PrePostProcessor::DeleteCleanup(NzbInfo* nzbInfo)
}
// delete old directory (if empty)
if (FileSystem::DirEmpty(nzbInfo->GetDestDir()))
{
FileSystem::RemoveDirectory(nzbInfo->GetDestDir());
}
FileSystem::DeleteDirectory(nzbInfo->GetDestDir());
}
}
void PrePostProcessor::CheckPostQueue()
void PrePostProcessor::CheckRequestPar(DownloadQueue* downloadQueue)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (!m_curJob && m_jobCount > 0)
{
m_curJob = GetNextJob(downloadQueue);
}
if (m_curJob)
{
PostInfo* postInfo = m_curJob->GetPostInfo();
if (!postInfo->GetWorking() && !IsNzbFileDownloading(m_curJob))
{
#ifndef DISABLE_PARCHECK
if (postInfo->GetRequestParCheck() &&
(postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped ||
(postInfo->GetForceRepair() && !postInfo->GetNzbInfo()->GetParFull())) &&
g_Options->GetParCheck() != Options::pcManual)
{
postInfo->SetForceParFull(postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped);
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psNone);
postInfo->SetRequestParCheck(false);
postInfo->SetStage(PostInfo::ptQueued);
postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
DeletePostThread(postInfo);
}
else if (postInfo->GetRequestParCheck() && postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
g_Options->GetParCheck() == Options::pcManual)
{
postInfo->SetRequestParCheck(false);
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psManual);
DeletePostThread(postInfo);
for (NzbInfo* postJob : m_activeJobs)
{
PostInfo* postInfo = postJob->GetPostInfo();
if (!postInfo->GetNzbInfo()->GetFileList()->empty())
if (postInfo->GetRequestParCheck() &&
(postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped ||
(postInfo->GetForceRepair() && !postInfo->GetNzbInfo()->GetParFull())) &&
g_Options->GetParCheck() != Options::pcManual)
{
postInfo->SetForceParFull(postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped);
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psNone);
postInfo->SetRequestParCheck(false);
postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
postInfo->SetWorking(false);
}
else if (postInfo->GetRequestParCheck() &&
postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
g_Options->GetParCheck() == Options::pcManual)
{
postInfo->SetRequestParCheck(false);
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psManual);
if (!postInfo->GetNzbInfo()->GetFileList()->empty())
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"Downloading all remaining files for manual par-check for %s", postInfo->GetNzbInfo()->GetName());
downloadQueue->EditEntry(postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, nullptr);
postInfo->SetStage(PostInfo::ptFinished);
}
else
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"There are no par-files remain for download for %s", postInfo->GetNzbInfo()->GetName());
}
postInfo->SetWorking(false);
}
}
#endif
}
void PrePostProcessor::CleanupJobs(DownloadQueue* downloadQueue)
{
m_activeJobs.erase(std::remove_if(m_activeJobs.begin(), m_activeJobs.end(),
[processor = this, downloadQueue](NzbInfo* postJob)
{
PostInfo* postInfo = postJob->GetPostInfo();
if (!postInfo->GetWorking() &&
!(postInfo->GetPostThread() && postInfo->GetPostThread()->IsRunning()))
{
delete postInfo->GetPostThread();
postInfo->SetPostThread(nullptr);
postInfo->SetStageTime(0);
postInfo->SetStageProgress(0);
postInfo->SetFileProgress(0);
postInfo->SetProgressLabel("");
if (postInfo->GetStartTime() > 0)
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"Downloading all remaining files for manual par-check for %s", postInfo->GetNzbInfo()->GetName());
downloadQueue->EditEntry(postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, 0, nullptr);
postInfo->SetStage(PostInfo::ptFinished);
postJob->SetPostTotalSec(postJob->GetPostTotalSec() +
(int)(Util::CurrentTime() - postInfo->GetStartTime()));
postInfo->SetStartTime(0);
}
if (postInfo->GetStage() == PostInfo::ptFinished || postInfo->GetDeleted())
{
processor->JobCompleted(downloadQueue, postInfo);
}
else
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"There are no par-files remain for download for %s", postInfo->GetNzbInfo()->GetName());
postInfo->SetStage(PostInfo::ptQueued);
}
return true;
}
#endif
if (postInfo->GetDeleted())
{
postInfo->SetStage(PostInfo::ptFinished);
}
if (postInfo->GetStage() == PostInfo::ptQueued &&
(!g_Options->GetPausePostProcess() || postInfo->GetNzbInfo()->GetForcePriority()))
{
DeletePostThread(postInfo);
StartJob(downloadQueue, postInfo);
}
else if (postInfo->GetStage() == PostInfo::ptFinished)
{
UpdatePauseState(false, nullptr);
JobCompleted(downloadQueue, postInfo);
}
else if (!g_Options->GetPausePostProcess())
{
error("Internal error: invalid state in post-processor");
// TODO: cancel (delete) current job
}
}
}
return false;
}),
m_activeJobs.end());
}
NzbInfo* PrePostProcessor::GetNextJob(DownloadQueue* downloadQueue)
bool PrePostProcessor::CanRunMoreJobs(bool* allowPar)
{
int totalJobs = (int)m_activeJobs.size();
int parJobs = 0;
int otherJobs = 0;
bool repairJobs = false;
for (NzbInfo* postJob : m_activeJobs)
{
bool parJob = postJob->GetPostInfo()->GetStage() >= PostInfo::ptLoadingPars &&
postJob->GetPostInfo()->GetStage() <= PostInfo::ptVerifyingRepaired;
repairJobs |= postJob->GetPostInfo()->GetStage() == PostInfo::ptRepairing;
parJobs += parJob ? 1 : 0;
otherJobs += parJob ? 0 : 1;
}
switch (g_Options->GetPostStrategy())
{
case Options::ppSequential:
*allowPar = true;
return totalJobs == 0;
case Options::ppBalanced:
*allowPar = parJobs == 0;
return otherJobs == 0 && (parJobs == 0 || repairJobs);
case Options::ppAggressive:
*allowPar = parJobs < 1;
return totalJobs < 3;
case Options::ppRocket:
*allowPar = parJobs < 2;
return totalJobs < 6;
}
return false;
}
NzbInfo* PrePostProcessor::PickNextJob(DownloadQueue* downloadQueue, bool allowPar)
{
NzbInfo* nzbInfo = nullptr;
for (NzbInfo* nzbInfo1: downloadQueue->GetQueue())
{
if (nzbInfo1->GetPostInfo() && !g_QueueScriptCoordinator->HasJob(nzbInfo1->GetId(), nullptr) &&
if (nzbInfo1->GetPostInfo() && !nzbInfo1->GetPostInfo()->GetWorking() &&
!g_QueueScriptCoordinator->HasJob(nzbInfo1->GetId(), nullptr) &&
nzbInfo1->GetDirectUnpackStatus() != NzbInfo::nsRunning &&
(!nzbInfo || nzbInfo1->GetPriority() > nzbInfo->GetPriority()) &&
(!g_Options->GetPausePostProcess() || nzbInfo1->GetForcePriority()))
(!g_Options->GetPausePostProcess() || nzbInfo1->GetForcePriority()) &&
(allowPar || !nzbInfo1->GetPostInfo()->GetNeedParCheck()) &&
(std::find(m_activeJobs.begin(), m_activeJobs.end(), nzbInfo1) == m_activeJobs.end()) &&
nzbInfo1->IsDownloadCompleted(true))
{
nzbInfo = nzbInfo1;
}
@@ -412,73 +586,92 @@ NzbInfo* PrePostProcessor::GetNextJob(DownloadQueue* downloadQueue)
return nzbInfo;
}
/**
* Reset the state of items after reloading from disk and
* delete items which could not be resumed.
* Also count the number of post-jobs.
*/
void PrePostProcessor::SanitisePostQueue()
void PrePostProcessor::CheckPostQueue()
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
size_t countBefore = m_activeJobs.size();
CheckRequestPar(downloadQueue);
CleanupJobs(downloadQueue);
bool changed = m_activeJobs.size() != countBefore;
bool allowPar;
while (CanRunMoreJobs(&allowPar) && !IsStopped())
{
PostInfo* postInfo = nzbInfo->GetPostInfo();
if (postInfo)
NzbInfo* postJob = PickNextJob(downloadQueue, allowPar);
if (!postJob)
{
m_jobCount++;
if (postInfo->GetStage() == PostInfo::ptExecutingScript ||
!FileSystem::DirectoryExists(nzbInfo->GetDestDir()))
{
postInfo->SetStage(PostInfo::ptFinished);
}
else
{
postInfo->SetStage(PostInfo::ptQueued);
}
break;
}
m_activeJobs.push_back(postJob);
PostInfo* postInfo = postJob->GetPostInfo();
if (postInfo->GetStage() == PostInfo::ptQueued &&
(!g_Options->GetPausePostProcess() || postInfo->GetNzbInfo()->GetForcePriority()))
{
StartJob(downloadQueue, postInfo, allowPar);
CheckRequestPar(downloadQueue);
CleanupJobs(downloadQueue);
changed = true;
}
}
if (changed)
{
downloadQueue->Save();
UpdatePauseState();
}
Util::SetStandByMode(m_activeJobs.empty());
}
void PrePostProcessor::DeletePostThread(PostInfo* postInfo)
{
delete postInfo->GetPostThread();
postInfo->SetPostThread(nullptr);
}
void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo)
void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo, bool allowPar)
{
if (!postInfo->GetStartTime())
{
postInfo->SetStartTime(Util::CurrentTime());
}
postInfo->SetStageTime(Util::CurrentTime());
postInfo->SetStageProgress(0);
postInfo->SetFileProgress(0);
postInfo->SetProgressLabel("");
#ifndef DISABLE_PARCHECK
if (postInfo->GetNzbInfo()->GetRenameStatus() == NzbInfo::rsNone &&
postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone)
if (postInfo->GetNzbInfo()->GetParRenameStatus() == NzbInfo::rsNone &&
postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone &&
g_Options->GetParRename())
{
UpdatePauseState(g_Options->GetParPauseQueue(), "par-rename");
m_parCoordinator.StartParRenameJob(postInfo);
EnterStage(downloadQueue, postInfo, PostInfo::ptParRenaming);
RenameController::StartJob(postInfo, RenameController::jkPar);
return;
}
else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psNone &&
#ifndef DISABLE_PARCHECK
if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psNone &&
postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone)
{
if (ParParser::FindMainPars(postInfo->GetNzbInfo()->GetDestDir(), nullptr))
{
UpdatePauseState(g_Options->GetParPauseQueue(), "par-check");
m_parCoordinator.StartParCheckJob(postInfo);
if (!allowPar)
{
postInfo->SetNeedParCheck(true);
return;
}
EnterStage(downloadQueue, postInfo, PostInfo::ptLoadingPars);
postInfo->SetNeedParCheck(false);
RepairController::StartJob(postInfo);
}
else
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"Nothing to par-check for %s", postInfo->GetNzbInfo()->GetName());
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psSkipped);
postInfo->SetWorking(false);
postInfo->SetStage(PostInfo::ptQueued);
}
return;
}
else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
((g_Options->GetParScan() != Options::psDupe &&
postInfo->GetNzbInfo()->CalcHealth() < postInfo->GetNzbInfo()->CalcCriticalHealth(false) &&
postInfo->GetNzbInfo()->CalcCriticalHealth(false) < 1000) ||
@@ -500,7 +693,8 @@ void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo
postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psFailure);
return;
}
else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
postInfo->GetNzbInfo()->GetFailedSize() - postInfo->GetNzbInfo()->GetParFailedSize() > 0 &&
ParParser::FindMainPars(postInfo->GetNzbInfo()->GetDestDir(), nullptr))
{
@@ -513,15 +707,23 @@ void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo
#endif
NzbParameter* unpackParameter = postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:", false);
bool unpackParam = !(unpackParameter && !strcasecmp(unpackParameter->GetValue(), "no"));
bool unpack = unpackParam && postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone &&
bool wantUnpack = !(unpackParameter && !strcasecmp(unpackParameter->GetValue(), "no"));
bool unpack = wantUnpack && postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone &&
postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone;
if (postInfo->GetNzbInfo()->GetRarRenameStatus() == NzbInfo::rsNone &&
unpack && g_Options->GetRarRename())
{
EnterStage(downloadQueue, postInfo, PostInfo::ptRarRenaming);
RenameController::StartJob(postInfo, RenameController::jkRar);
return;
}
bool parFailed = postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psFailure ||
postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psRepairPossible ||
postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psManual;
bool cleanup = !unpack &&
bool cleanup = !unpack && wantUnpack &&
postInfo->GetNzbInfo()->GetCleanupStatus() == NzbInfo::csNone &&
!Util::EmptyStr(g_Options->GetExtCleanupDisk()) &&
((postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSuccess &&
@@ -548,8 +750,6 @@ void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo
!strncmp(postInfo->GetNzbInfo()->GetDestDir(), g_Options->GetInterDir(), strlen(g_Options->GetInterDir())) &&
postInfo->GetNzbInfo()->GetDestDir()[strlen(g_Options->GetInterDir())] == PATH_SEPARATOR;
bool postScript = true;
if (unpack && parFailed)
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkWarning,
@@ -559,123 +759,94 @@ void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo
unpack = false;
}
if (!unpack && !moveInter && !postScript)
{
postInfo->SetStage(PostInfo::ptFinished);
return;
}
postInfo->SetProgressLabel(unpack ? "Unpacking" : moveInter ? "Moving" : "Executing post-process-script");
postInfo->SetWorking(true);
postInfo->SetStage(unpack ? PostInfo::ptUnpacking : moveInter ? PostInfo::ptMoving : PostInfo::ptExecutingScript);
postInfo->SetFileProgress(0);
postInfo->SetStageProgress(0);
downloadQueue->Save();
postInfo->SetStageTime(Util::CurrentTime());
if (unpack)
{
UpdatePauseState(g_Options->GetUnpackPauseQueue(), "unpack");
EnterStage(downloadQueue, postInfo, PostInfo::ptUnpacking);
UnpackController::StartJob(postInfo);
}
else if (cleanup)
{
UpdatePauseState(g_Options->GetUnpackPauseQueue() || g_Options->GetScriptPauseQueue(), "cleanup");
EnterStage(downloadQueue, postInfo, PostInfo::ptCleaningUp);
CleanupController::StartJob(postInfo);
}
else if (moveInter)
{
UpdatePauseState(g_Options->GetUnpackPauseQueue() || g_Options->GetScriptPauseQueue(), "move");
EnterStage(downloadQueue, postInfo, PostInfo::ptMoving);
MoveController::StartJob(postInfo);
}
else
{
UpdatePauseState(g_Options->GetScriptPauseQueue(), "post-process-script");
EnterStage(downloadQueue, postInfo, PostInfo::ptExecutingScript);
PostScriptController::StartJob(postInfo);
}
}
void PrePostProcessor::EnterStage(DownloadQueue* downloadQueue, PostInfo* postInfo, PostInfo::EStage stage)
{
postInfo->SetWorking(true);
postInfo->SetStage(stage);
}
void PrePostProcessor::JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo)
{
NzbInfo* nzbInfo = postInfo->GetNzbInfo();
if (postInfo->GetStartTime() > 0)
{
nzbInfo->SetPostTotalSec((int)(Util::CurrentTime() - postInfo->GetStartTime()));
postInfo->SetStartTime(0);
}
DeletePostThread(postInfo);
nzbInfo->LeavePostProcess();
if (IsNzbFileCompleted(nzbInfo, true))
if (nzbInfo->IsDownloadCompleted(true))
{
NzbCompleted(downloadQueue, nzbInfo, false);
}
if (nzbInfo == m_curJob)
{
m_curJob = nullptr;
}
m_jobCount--;
downloadQueue->Save();
m_queuedJobs--;
}
bool PrePostProcessor::IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars)
void PrePostProcessor::UpdatePauseState()
{
if (nzbInfo->GetActiveDownloads())
bool needPause = false;
for (NzbInfo* postJob : m_activeJobs)
{
return false;
}
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if ((!fileInfo->GetPaused() || !ignorePausedPars || !fileInfo->GetParFile()) &&
!fileInfo->GetDeleted())
switch (postJob->GetPostInfo()->GetStage())
{
return false;
case PostInfo::ptLoadingPars:
case PostInfo::ptVerifyingSources:
case PostInfo::ptRepairing:
case PostInfo::ptVerifyingRepaired:
case PostInfo::ptParRenaming:
needPause |= g_Options->GetParPauseQueue();
break;
case PostInfo::ptRarRenaming:
case PostInfo::ptUnpacking:
case PostInfo::ptCleaningUp:
case PostInfo::ptMoving:
needPause |= g_Options->GetUnpackPauseQueue();
break;
case PostInfo::ptExecutingScript:
needPause |= g_Options->GetScriptPauseQueue();
break;
case PostInfo::ptQueued:
case PostInfo::ptFinished:
break;
}
}
return true;
}
bool PrePostProcessor::IsNzbFileDownloading(NzbInfo* nzbInfo)
{
if (nzbInfo->GetActiveDownloads())
{
return true;
}
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if (!fileInfo->GetPaused())
{
return true;
}
}
return false;
}
void PrePostProcessor::UpdatePauseState(bool needPause, const char* reason)
{
if (needPause && !g_Options->GetTempPauseDownload())
{
info("Pausing download before %s", reason);
info("Pausing download before post-processing");
}
else if (!needPause && g_Options->GetTempPauseDownload())
{
info("Unpausing download after %s", m_pauseReason);
info("Unpausing download after post-processing");
}
g_Options->SetTempPauseDownload(needPause);
m_pauseReason = reason;
}
bool PrePostProcessor::EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
bool PrePostProcessor::EditList(DownloadQueue* downloadQueue, IdList* idList,
DownloadQueue::EEditAction action, const char* args)
{
debug("Edit-command for post-processor received");
switch (action)
@@ -704,22 +875,17 @@ bool PrePostProcessor::PostQueueDelete(DownloadQueue* downloadQueue, IdList* idL
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"Deleting active post-job %s", postInfo->GetNzbInfo()->GetName());
postInfo->SetDeleted(true);
#ifndef DISABLE_PARCHECK
if (PostInfo::ptLoadingPars <= postInfo->GetStage() && postInfo->GetStage() <= PostInfo::ptRenaming)
{
if (m_parCoordinator.Cancel())
{
ok = true;
}
}
else
#endif
if (postInfo->GetPostThread())
{
debug("Terminating %s for %s", (postInfo->GetStage() == PostInfo::ptUnpacking ? "unpack" : "post-process-script"), postInfo->GetNzbInfo()->GetName());
debug("Terminating post-process thread for %s", postInfo->GetNzbInfo()->GetName());
postInfo->GetPostThread()->Stop();
ok = true;
}
else if (postInfo->GetNzbInfo()->GetUnpackThread())
{
((DirectUnpack*)postInfo->GetNzbInfo()->GetUnpackThread())->NzbDeleted(downloadQueue, postInfo->GetNzbInfo());
ok = true;
}
else
{
error("Internal error in PrePostProcessor::QueueDelete");
@@ -729,7 +895,16 @@ bool PrePostProcessor::PostQueueDelete(DownloadQueue* downloadQueue, IdList* idL
{
postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
"Deleting queued post-job %s", postInfo->GetNzbInfo()->GetName());
JobCompleted(downloadQueue, postInfo);
m_activeJobs.erase(std::remove_if(m_activeJobs.begin(), m_activeJobs.end(),
[postInfo](NzbInfo* postJob)
{
return postInfo == postJob->GetPostInfo();
}),
m_activeJobs.end());
ok = true;
}
break;
@@ -737,5 +912,39 @@ bool PrePostProcessor::PostQueueDelete(DownloadQueue* downloadQueue, IdList* idL
}
}
if (ok)
{
downloadQueue->Save();
}
return ok;
}
void PrePostProcessor::FileDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, FileInfo* fileInfo)
{
if (!nzbInfo->GetPostInfo())
{
g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeFileDownloaded);
}
if (g_Options->GetDirectUnpack() && g_Options->GetDecode())
{
bool allowPar;
if (nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsNone &&
nzbInfo->GetDirectRenameStatus() != NzbInfo::tsRunning &&
DirectUnpack::IsArchiveFilename(fileInfo->GetFilename()) &&
CanRunMoreJobs(&allowPar))
{
NzbParameter* unpackParameter = nzbInfo->GetParameters()->Find("*Unpack:", false);
bool wantUnpack = !(unpackParameter && !strcasecmp(unpackParameter->GetValue(), "no"));
if (wantUnpack && nzbInfo->GetFailedArticles() == 0)
{
DirectUnpack::StartJob(nzbInfo);
}
}
else if (nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsRunning)
{
((DirectUnpack*)nzbInfo->GetUnpackThread())->FileDownloaded(downloadQueue, fileInfo);
}
}
}

View File

@@ -24,50 +24,45 @@
#include "Thread.h"
#include "Observer.h"
#include "DownloadInfo.h"
#include "ParCoordinator.h"
class PrePostProcessor : public Thread
class PrePostProcessor : public Thread, public Observer
{
public:
PrePostProcessor();
virtual void Run();
virtual void Stop();
bool HasMoreJobs() { return m_jobCount > 0; }
int GetJobCount() { return m_jobCount; }
bool HasMoreJobs() { return m_queuedJobs > 0; }
int GetJobCount() { return m_queuedJobs; }
bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action,
int offset, const char* text);
const char* args);
void NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
protected:
virtual void Update(Subject* caller, void* aspect) { DownloadQueueUpdate(aspect); }
private:
class DownloadQueueObserver: public Observer
{
public:
PrePostProcessor* m_owner;
virtual void Update(Subject* Caller, void* Aspect) { m_owner->DownloadQueueUpdate(Caller, Aspect); }
};
int m_queuedJobs = 0;
RawNzbList m_activeJobs;
ParCoordinator m_parCoordinator;
DownloadQueueObserver m_downloadQueueObserver;
int m_jobCount = 0;
NzbInfo* m_curJob = nullptr;
const char* m_pauseReason = nullptr;
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 CheckRequestPar(DownloadQueue* downloadQueue);
void CleanupJobs(DownloadQueue* downloadQueue);
bool CanRunMoreJobs(bool* allowPar);
NzbInfo* PickNextJob(DownloadQueue* downloadQueue, bool allowPar);
void StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo, bool allowPar);
void EnterStage(DownloadQueue* downloadQueue, PostInfo* postInfo, PostInfo::EStage stage);
void SanitisePostQueue();
void UpdatePauseState(bool needPause, const char* reason);
void UpdatePauseState();
void NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void NzbCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, bool saveQueue);
void JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo);
bool PostQueueDelete(DownloadQueue* downloadQueue, IdList* idList);
void DeletePostThread(PostInfo* postInfo);
NzbInfo* GetNextJob(DownloadQueue* downloadQueue);
void DownloadQueueUpdate(Subject* Caller, void* Aspect);
void DownloadQueueUpdate(void* aspect);
void DeleteCleanup(NzbInfo* nzbInfo);
void WaitJobs();
void FileDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, FileInfo* fileInfo);
};
extern PrePostProcessor* g_PrePostProcessor;

View File

@@ -0,0 +1,730 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "RarReader.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
// RAR3 constants
static const uint16 RAR3_MAIN_VOLUME = 0x0001;
static const uint16 RAR3_MAIN_NEWNUMBERING = 0x0010;
static const uint16 RAR3_MAIN_PASSWORD = 0x0080;
static const uint8 RAR3_BLOCK_MAIN = 0x73; // s
static const uint8 RAR3_BLOCK_FILE = 0x74; // t
static const uint8 RAR3_BLOCK_ENDARC = 0x7b; // {
static const uint16 RAR3_BLOCK_ADDSIZE = 0x8000;
static const uint16 RAR3_FILE_ADDSIZE = 0x0100;
static const uint16 RAR3_FILE_SPLITBEFORE = 0x0001;
static const uint16 RAR3_FILE_SPLITAFTER = 0x0002;
static const uint16 RAR3_ENDARC_NEXTVOL = 0x0001;
static const uint16 RAR3_ENDARC_DATACRC = 0x0002;
static const uint16 RAR3_ENDARC_VOLNUMBER = 0x0008;
// RAR5 constants
static const uint8 RAR5_BLOCK_MAIN = 1;
static const uint8 RAR5_BLOCK_FILE = 2;
static const uint8 RAR5_BLOCK_ENCRYPTION = 4;
static const uint8 RAR5_BLOCK_ENDARC = 5;
static const uint8 RAR5_BLOCK_EXTRADATA = 0x01;
static const uint8 RAR5_BLOCK_DATAAREA = 0x02;
static const uint8 RAR5_BLOCK_SPLITBEFORE = 0x08;
static const uint8 RAR5_BLOCK_SPLITAFTER = 0x10;
static const uint8 RAR5_MAIN_ISVOL = 0x01;
static const uint8 RAR5_MAIN_VOLNR = 0x02;
static const uint8 RAR5_FILE_TIME = 0x02;
static const uint8 RAR5_FILE_CRC = 0x04;
static const uint8 RAR5_FILE_EXTRATIME = 0x03;
static const uint8 RAR5_FILE_EXTRATIMEUNIXFORMAT = 0x01;
static const uint8 RAR5_ENDARC_NEXTVOL = 0x01;
bool RarVolume::Read()
{
debug("Checking file %s", *m_filename);
DiskFile file;
if (!file.Open(m_filename, DiskFile::omRead))
{
return false;
}
m_version = DetectRarVersion(file);
file.Seek(0);
bool ok = false;
switch (m_version)
{
case 3:
ok = ReadRar3Volume(file);
break;
case 5:
ok = ReadRar5Volume(file);
break;
}
file.Close();
DecryptFree();
LogDebugInfo();
return ok;
}
int RarVolume::DetectRarVersion(DiskFile& file)
{
static char RAR3_SIGNATURE[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 };
static char RAR5_SIGNATURE[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00 };
char fileSignature[8];
int cnt = 0;
cnt = (int)file.Read(fileSignature, sizeof(fileSignature));
bool rar5 = cnt == sizeof(fileSignature) && !strcmp(RAR5_SIGNATURE, fileSignature);
bool rar3 = !rar5 && cnt == sizeof(fileSignature) && !strcmp(RAR3_SIGNATURE, fileSignature);
return rar3 ? 3 : rar5 ? 5 : 0;
}
bool RarVolume::Read(DiskFile& file, RarBlock* block, void* buffer, int64 size)
{
if (m_encrypted)
{
if (!DecryptRead(file, buffer, size)) return false;
}
else
{
if (file.Read(buffer, size) != size) return false;
}
if (block)
{
block->trailsize -= size;
}
return true;
}
bool RarVolume::Read16(DiskFile& file, RarBlock* block, uint16* result)
{
uint8 buf[2];
if (!Read(file, block, buf, sizeof(buf))) return false;
*result = ((uint16)buf[1] << 8) + buf[0];
return true;
}
bool RarVolume::Read32(DiskFile& file, RarBlock* block, uint32* result)
{
uint8 buf[4];
if (!Read(file, block, buf, sizeof(buf))) return false;
*result = ((uint32)buf[3] << 24) + ((uint32)buf[2] << 16) + ((uint32)buf[1] << 8) + buf[0];
return true;
}
bool RarVolume::ReadV(DiskFile& file, RarBlock* block, uint64* result)
{
*result = 0;
uint8 val;
uint8 bits = 0;
do
{
if (Read(file, block, &val, sizeof(val)) != sizeof(val)) return false;
*result += (uint64)(val & 0x7f) << bits;
bits += 7;
} while (val & 0x80);
return true;
}
bool RarVolume::Skip(DiskFile& file, RarBlock* block, int64 size)
{
uint8 buf[256];
while (size > 0)
{
int64 len = size <= sizeof(buf) ? size : sizeof(buf);
if (!Read(file, block, buf, len)) return false;
size -= len;
}
return true;
}
bool RarVolume::ReadRar3Volume(DiskFile& file)
{
debug("Reading rar3-file %s", *m_filename);
while (!file.Eof())
{
RarBlock block = ReadRar3Block(file);
if (!block.type)
{
return false;
}
if (block.type == RAR3_BLOCK_MAIN)
{
if (block.flags & RAR3_MAIN_PASSWORD)
{
m_encrypted = true;
if (m_password.Empty()) return false;
}
m_newNaming = block.flags & RAR3_MAIN_NEWNUMBERING;
m_multiVolume = block.flags & RAR3_MAIN_VOLUME;
}
else if (block.type == RAR3_BLOCK_FILE)
{
RarFile innerFile;
if (!ReadRar3File(file, block, innerFile)) return false;
m_files.push_back(std::move(innerFile));
}
else if (block.type == RAR3_BLOCK_ENDARC)
{
if (block.flags & RAR3_ENDARC_DATACRC)
{
if (!Skip(file, &block, 4)) return false;
}
if (block.flags & RAR3_ENDARC_VOLNUMBER)
{
if (!Read32(file, &block, &m_volumeNo)) return false;
m_hasNextVolume = (block.flags & RAR3_ENDARC_NEXTVOL) != 0;
}
break;
}
else if (block.type < 0x72 || block.type > 0x7b)
{
// inlvaid block type
return false;
}
uint64 skip = block.trailsize;
if (m_encrypted)
{
skip -= 16 - m_decryptPos;
m_decryptPos = 16;
DecryptFree();
}
if (!file.Seek(skip, DiskFile::soCur))
{
return false;
}
}
return true;
}
RarVolume::RarBlock RarVolume::ReadRar3Block(DiskFile& file)
{
RarBlock block {0};
uint8 salt[8];
if (m_encrypted &&
!(file.Read(salt, sizeof(salt)) == sizeof(salt) &&
DecryptRar3Prepare(salt) && DecryptInit(128)))
{
return {0};
}
uint8 buf[7];
if (!Read(file, nullptr, &buf, sizeof(buf))) return {0};
block.crc = ((uint16)buf[1] << 8) + buf[0];
block.type = buf[2];
block.flags = ((uint16)buf[4] << 8) + buf[3];
uint16 size = ((uint16)buf[6] << 8) + buf[5];
uint32 blocksize = size;
block.trailsize = blocksize - sizeof(buf);
uint8 addbuf[4];
if ((block.flags & RAR3_BLOCK_ADDSIZE) && !Read(file, nullptr, &addbuf, sizeof(addbuf)))
{
return {0};
}
block.addsize = ((uint32)addbuf[3] << 24) + ((uint32)addbuf[2] << 16) + ((uint32)addbuf[1] << 8) + addbuf[0];
if (block.flags & RAR3_BLOCK_ADDSIZE)
{
blocksize += (uint32)block.addsize;
block.trailsize = blocksize - sizeof(buf) - 4;
}
static int num = 0;
debug("%i) %llu, %i, %i, %i, %u, %llu", ++num, (long long)block.crc, (int)block.type, (int)block.flags, (int)size, (int)block.addsize, (long long)block.trailsize);
return block;
}
bool RarVolume::ReadRar3File(DiskFile& file, RarBlock& block, RarFile& innerFile)
{
innerFile.m_splitBefore = block.flags & RAR3_FILE_SPLITBEFORE;
innerFile.m_splitAfter = block.flags & RAR3_FILE_SPLITAFTER;
uint16 namelen;
uint32 size;
if (!Read32(file, &block, &size)) return false;
innerFile.m_size = size;
if (!Skip(file, &block, 1)) return false;
if (!Skip(file, &block, 4)) return false;
if (!Read32(file, &block, &innerFile.m_time)) return false;
if (!Skip(file, &block, 2)) return false;
if (!Read16(file, &block, &namelen)) return false;
if (!Read32(file, &block, &innerFile.m_attr)) return false;
if (block.flags & RAR3_FILE_ADDSIZE)
{
uint32 highsize;
if (!Read32(file, &block, &highsize)) return false;
block.trailsize += (uint64)highsize << 32;
if (!Read32(file, &block, &highsize)) return false;
innerFile.m_size += (uint64)highsize << 32;
}
if (namelen > 8192) return false; // an error
CharBuffer name;
name.Reserve(namelen + 1);
if (!Read(file, &block, (char*)name, namelen)) return false;
name[namelen] = '\0';
innerFile.m_filename = name;
debug("%i, %i, %s", (int)block.trailsize, (int)namelen, (const char*)name);
return true;
}
bool RarVolume::ReadRar5Volume(DiskFile& file)
{
debug("Reading rar5-file %s", *m_filename);
file.Seek(8);
while (!file.Eof())
{
RarBlock block = ReadRar5Block(file);
if (!block.type)
{
return false;
}
if (block.type == RAR5_BLOCK_MAIN)
{
uint64 arcflags;
if (!ReadV(file, &block, &arcflags)) return false;
if (arcflags & RAR5_MAIN_VOLNR)
{
uint64 volnr;
if (!ReadV(file, &block, &volnr)) return false;
m_volumeNo = (uint32)volnr;
}
m_newNaming = true;
m_multiVolume = (arcflags & RAR5_MAIN_ISVOL) != 0;
}
else if (block.type == RAR5_BLOCK_ENCRYPTION)
{
uint64 val;
if (!ReadV(file, &block, &val)) return false;
if (val != 0) return false; // supporting only AES
if (!ReadV(file, &block, &val)) return false;
uint8 kdfCount;
uint8 salt[16];
if (!Read(file, &block, &kdfCount, sizeof(kdfCount))) return false;
if (!Read(file, &block, &salt, sizeof(salt))) return false;
m_encrypted = true;
if (m_password.Empty()) return false;
if (!DecryptRar5Prepare(kdfCount, salt)) return false;
}
else if (block.type == RAR5_BLOCK_FILE)
{
RarFile innerFile;
if (!ReadRar5File(file, block, innerFile)) return false;
m_files.push_back(std::move(innerFile));
}
else if (block.type == RAR5_BLOCK_ENDARC)
{
uint64 endflags;
if (!ReadV(file, &block, &endflags)) return false;
m_hasNextVolume = (endflags & RAR5_ENDARC_NEXTVOL) != 0;
break;
}
else if (block.type < 1 || block.type > 5)
{
// inlvaid block type
return false;
}
uint64 skip = block.trailsize;
if (m_encrypted)
{
skip -= 16 - m_decryptPos;
if (m_decryptPos < 16)
{
skip += skip % 16 > 0 ? 16 - skip % 16 : 0;
m_decryptPos = 16;
}
DecryptFree();
}
if (!file.Seek(skip, DiskFile::soCur))
{
return false;
}
}
return true;
}
RarVolume::RarBlock RarVolume::ReadRar5Block(DiskFile& file)
{
RarBlock block {0};
uint64 buf = 0;
if (m_encrypted &&
!(file.Read(m_decryptIV, sizeof(m_decryptIV)) == sizeof(m_decryptIV) &&
DecryptInit(256)))
{
return {0};
}
if (!Read32(file, nullptr, &block.crc)) return {0};
if (!ReadV(file, nullptr, &buf)) return {0};
uint32 size = (uint32)buf;
block.trailsize = size;
if (!ReadV(file, &block, &buf)) return {0};
block.type = (uint8)buf;
if (!ReadV(file, &block, &buf)) return {0};
block.flags = (uint16)buf;
block.addsize = 0;
if ((block.flags & RAR5_BLOCK_EXTRADATA) && !ReadV(file, &block, &block.addsize)) return {0};
uint64 datasize = 0;
if ((block.flags & RAR5_BLOCK_DATAAREA) && !ReadV(file, &block, &datasize)) return {0};
block.trailsize += datasize;
static int num = 0;
debug("%i) %llu, %i, %i, %i, %u, %llu", ++num, (long long)block.crc, (int)block.type, (int)block.flags, (int)size, (int)block.addsize, (long long)block.trailsize);
return block;
}
bool RarVolume::ReadRar5File(DiskFile& file, RarBlock& block, RarFile& innerFile)
{
innerFile.m_splitBefore = block.flags & RAR5_BLOCK_SPLITBEFORE;
innerFile.m_splitAfter = block.flags & RAR5_BLOCK_SPLITAFTER;
uint64 val;
uint64 fileflags;
if (!ReadV(file, &block, &fileflags)) return false;
if (!ReadV(file, &block, &val)) return false; // skip
innerFile.m_size = (int64)val;
if (!ReadV(file, &block, &val)) return false;
innerFile.m_attr = (uint32)val;
if (fileflags & RAR5_FILE_TIME && !Read32(file, &block, &innerFile.m_time)) return false;
if (fileflags & RAR5_FILE_CRC && !Skip(file, &block, 4)) return false;
if (!ReadV(file, &block, &val)) return false; // skip
if (!ReadV(file, &block, &val)) return false; // skip
uint64 namelen;
if (!ReadV(file, &block, &namelen)) return false;
if (namelen > 8192) return false; // an error
CharBuffer name;
name.Reserve((uint32)namelen + 1);
if (!Read(file, &block, (char*)name, namelen)) return false;
name[namelen] = '\0';
innerFile.m_filename = name;
// reading extra headers to find file time
if (block.flags & RAR5_BLOCK_EXTRADATA)
{
uint64 remsize = block.addsize;
while (remsize > 0)
{
uint64 trailsize = block.trailsize;
uint64 len;
if (!ReadV(file, &block, &len)) return false;
remsize -= trailsize - block.trailsize + len;
trailsize = block.trailsize;
uint64 type;
if (!ReadV(file, &block, &type)) return false;
if (type == RAR5_FILE_EXTRATIME)
{
uint64 flags;
if (!ReadV(file, &block, &flags)) return false;
if (flags & RAR5_FILE_EXTRATIMEUNIXFORMAT)
{
if (!Read32(file, &block, &innerFile.m_time)) return false;
}
else
{
uint32 timelow, timehigh;
if (!Read32(file, &block, &timelow)) return false;
if (!Read32(file, &block, &timehigh)) return false;
uint64 wintime = ((uint64)timehigh << 32) + timelow;
innerFile.m_time = (uint32)(wintime / 10000000 - 11644473600LL);
}
}
len -= trailsize - block.trailsize;
if (!Skip(file, &block, len)) return false;
}
}
debug("%llu, %i, %s", (long long)block.trailsize, (int)namelen, (const char*)name);
return true;
}
void RarVolume::LogDebugInfo()
{
debug("Volume: version:%i, multi:%i, vol-no:%i, new-naming:%i, has-next:%i, encrypted:%i, file-count:%i, [%s]",
(int)m_version, (int)m_multiVolume, m_volumeNo, (int)m_newNaming, (int)m_hasNextVolume,
(int)m_encrypted, (int)m_files.size(), FileSystem::BaseFileName(m_filename));
for (RarFile& file : m_files)
{
debug(" time:%i, size:%lli, attr:%i, split-before:%i, split-after:%i, [%s]",
(int)file.m_time, (long long)file.m_size, (int)file.m_attr,
(int)file.m_splitBefore, (int)file.m_splitAfter, *file.m_filename);
}
}
bool RarVolume::DecryptRar3Prepare(const uint8 salt[8])
{
WString wstr(*m_password);
int len = wstr.Length();
if (len == 0) return false;
CharBuffer seed(len * 2 + 8);
for (int i = 0; i < len; i++)
{
wchar_t ch = wstr[i];
seed[i * 2] = ch & 0xFF;
seed[i * 2 + 1] = (ch & 0xFF00) >> 8;
}
memcpy(seed + len * 2, salt, 8);
debug("seed: %s", *Util::FormatBuffer((const char*)seed, seed.Size()));
#ifdef HAVE_OPENSSL
EVP_MD_CTX* context = EVP_MD_CTX_create();
if (!EVP_DigestInit(context, EVP_sha1()))
{
EVP_MD_CTX_destroy(context);
return false;
}
#elif defined(HAVE_NETTLE)
sha1_ctx context;
sha1_init(&context);
#else
return false;
#endif
uint8 digest[20];
const int rounds = 0x40000;
for (int i = 0; i < rounds; i++)
{
#ifdef HAVE_OPENSSL
EVP_DigestUpdate(context, *seed, seed.Size());
#elif defined(HAVE_NETTLE)
sha1_update(&context, seed.Size(), (const uint8_t*)*seed);
#endif
uint8 buf[3];
buf[0] = (uint8)i;
buf[1] = (uint8)(i >> 8);
buf[2] = (uint8)(i >> 16);
#ifdef HAVE_OPENSSL
EVP_DigestUpdate(context, buf, sizeof(buf));
#elif defined(HAVE_NETTLE)
sha1_update(&context, sizeof(buf), buf);
#endif
if (i % (rounds / 16) == 0)
{
#ifdef HAVE_OPENSSL
EVP_MD_CTX* ivContext = EVP_MD_CTX_create();
EVP_MD_CTX_copy(ivContext, context);
EVP_DigestFinal(ivContext, digest, nullptr);
EVP_MD_CTX_destroy(ivContext);
#elif defined(HAVE_NETTLE)
sha1_ctx ivContext = context;
sha1_digest(&ivContext, sizeof(digest), digest);
#endif
m_decryptIV[i / (rounds / 16)] = digest[sizeof(digest) - 1];
}
}
#ifdef HAVE_OPENSSL
EVP_DigestFinal(context, digest, nullptr);
EVP_MD_CTX_destroy(context);
#elif defined(HAVE_NETTLE)
sha1_digest(&context, sizeof(digest), digest);
#endif
debug("digest: %s", *Util::FormatBuffer((const char*)digest, sizeof(digest)));
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
m_decryptKey[i * 4 + j] = digest[i * 4 + 3 - j];
}
}
debug("key: %s", *Util::FormatBuffer((const char*)m_decryptKey, sizeof(m_decryptKey)));
debug("iv: %s", *Util::FormatBuffer((const char*)m_decryptIV, sizeof(m_decryptIV)));
return true;
}
bool RarVolume::DecryptRar5Prepare(uint8 kdfCount, const uint8 salt[16])
{
if (kdfCount > 24) return false;
int iterations = 1 << kdfCount;
#ifdef HAVE_OPENSSL
if (!PKCS5_PBKDF2_HMAC(m_password, m_password.Length(), salt, 16,
iterations, EVP_sha256(), sizeof(m_decryptKey), m_decryptKey)) return false;
return true;
#elif defined(HAVE_NETTLE)
pbkdf2_hmac_sha256(m_password.Length(), (const uint8_t*)*m_password,
iterations, 16, salt, sizeof(m_decryptKey), m_decryptKey);
return true;
#else
return false;
#endif
}
bool RarVolume::DecryptInit(int keyLength)
{
#ifdef HAVE_OPENSSL
if (!(m_context = EVP_CIPHER_CTX_new())) return false;
if (!EVP_DecryptInit((EVP_CIPHER_CTX*)m_context,
keyLength == 128 ? EVP_aes_128_cbc() : EVP_aes_256_cbc(),
m_decryptKey, m_decryptIV))
return false;
return true;
#elif defined(HAVE_NETTLE)
m_context = new aes_ctx;
aes_set_decrypt_key((aes_ctx*)m_context, keyLength == 128 ? 16 : 32, m_decryptKey);
return true;
#else
return false;
#endif
}
bool RarVolume::DecryptBuf(const uint8 in[16], uint8 out[16])
{
#ifdef HAVE_OPENSSL
uint8 outbuf[32];
int outlen = 0;
if (!EVP_DecryptUpdate((EVP_CIPHER_CTX*)m_context, outbuf, &outlen, in, 16)) return false;
memcpy(out, outbuf + outlen, 16);
debug("decrypted: %s", *Util::FormatBuffer((const char*)out, 16));
return true;
#elif defined(HAVE_NETTLE)
aes_decrypt((aes_ctx*)m_context, 16, out, in);
for (int i = 0; i < 16; i++)
{
out[i] ^= m_decryptIV[i];
}
memcpy(m_decryptIV, in, 16);
debug("decrypted: %s", *Util::FormatBuffer((const char*)out, 16));
return true;
#else
return false;
#endif
}
void RarVolume::DecryptFree()
{
if (m_context)
{
#ifdef HAVE_OPENSSL
EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)m_context);
#elif defined(HAVE_NETTLE)
delete (aes_ctx*)m_context;
#endif
m_context = nullptr;
}
}
bool RarVolume::DecryptRead(DiskFile& file, void* buffer, int64 size)
{
while (size > 0)
{
if (m_decryptPos >= 16)
{
uint8 buf[16];
if (file.Read(&buf, sizeof(buf)) != sizeof(buf)) return false;
m_decryptPos = 0;
if (!DecryptBuf(buf, m_decryptBuf)) return false;
}
uint8 remainingBuf = 16 - m_decryptPos;
uint8 len = size <= remainingBuf ? (uint8)size : remainingBuf;
memcpy(buffer, m_decryptBuf + m_decryptPos, len);
m_decryptPos += len;
size -= len;
buffer = (char*)buffer + len;
}
return true;
}

View File

@@ -0,0 +1,114 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RARREADER_H
#define RARREADER_H
#include "NString.h"
#include "Log.h"
#include "FileSystem.h"
class RarFile
{
public:
const char* GetFilename() { return m_filename; }
uint32 GetTime() { return m_time; }
uint32 GetAttr() { return m_attr; }
int64 GetSize() { return m_size; }
bool GetSplitBefore() { return m_splitBefore; }
bool GetSplitAfter() { return m_splitAfter; }
private:
CString m_filename;
uint32 m_time = 0;
uint32 m_attr = 0;
int64 m_size = 0;
bool m_splitBefore = false;
bool m_splitAfter = false;
friend class RarVolume;
};
class RarVolume
{
public:
typedef std::deque<RarFile> FileList;
RarVolume(const char* filename) : m_filename(filename) {}
bool Read();
const char* GetFilename() { return m_filename; }
int GetVersion() { return m_version; }
uint32 GetVolumeNo() { return m_volumeNo; }
bool GetNewNaming() { return m_newNaming; }
bool GetHasNextVolume() { return m_hasNextVolume; }
bool GetMultiVolume() { return m_multiVolume; }
bool GetEncrypted() { return m_encrypted; }
void SetPassword(const char* password) { m_password = password; }
FileList* GetFiles() { return &m_files; }
private:
struct RarBlock
{
uint32 crc;
uint8 type;
uint16 flags;
uint64 addsize;
uint64 trailsize;
};
CString m_filename;
int m_version = 0;
uint32 m_volumeNo = 0;
bool m_newNaming = false;
bool m_hasNextVolume = false;
bool m_multiVolume = false;
FileList m_files;
bool m_encrypted = false;
CString m_password;
uint8 m_decryptKey[32];
uint8 m_decryptIV[16];
uint8 m_decryptBuf[16];
uint8 m_decryptPos = 16;
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
void* m_context = nullptr;
void* m_session = nullptr;
int DetectRarVersion(DiskFile& file);
void LogDebugInfo();
bool Skip(DiskFile& file, RarBlock* block, int64 size);
bool Read(DiskFile& file, RarBlock* block, void* buffer, int64 size);
bool Read16(DiskFile& file, RarBlock* block, uint16* result);
bool Read32(DiskFile& file, RarBlock* block, uint32* result);
bool ReadV(DiskFile& file, RarBlock* block, uint64* result);
bool ReadRar3Volume(DiskFile& file);
bool ReadRar5Volume(DiskFile& file);
RarBlock ReadRar3Block(DiskFile& file);
RarBlock ReadRar5Block(DiskFile& file);
bool ReadRar3File(DiskFile& file, RarBlock& block, RarFile& innerFile);
bool ReadRar5File(DiskFile& file, RarBlock& block, RarFile& innerFile);
bool DecryptRar3Prepare(const uint8 salt[8]);
bool DecryptRar5Prepare(uint8 kdfCount, const uint8 salt[16]);
bool DecryptInit(int keyLength);
bool DecryptBuf(const uint8 in[16], uint8 out[16]);
void DecryptFree();
bool DecryptRead(DiskFile& file, void* buffer, int64 size);
};
#endif

View File

@@ -0,0 +1,325 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "RarRenamer.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
void RarRenamer::Execute()
{
m_progressLabel.Format("Checking renamed rar-files for %s", *m_infoName);
m_stageProgress = 0;
UpdateProgress();
BuildDirList(m_destDir);
for (CString& destDir : m_dirList)
{
debug("Checking %s", *destDir);
CheckFiles(destDir);
}
}
void RarRenamer::BuildDirList(const char* destDir)
{
m_dirList.push_back(destDir);
DirBrowser dirBrowser(destDir);
while (const char* filename = dirBrowser.Next())
{
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (FileSystem::DirectoryExists(fullFilename))
{
BuildDirList(fullFilename);
}
else
{
m_fileCount++;
}
}
}
}
void RarRenamer::CheckFiles(const char* destDir)
{
DirBrowser dir(destDir);
while (const char* filename = dir.Next())
{
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (!FileSystem::DirectoryExists(fullFilename))
{
m_progressLabel.Format("Checking file %s", filename);
m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
UpdateProgress();
m_curFile++;
CheckOneFile(fullFilename);
}
}
}
if (!m_volumes.empty())
{
RenameFiles(destDir);
}
}
void RarRenamer::CheckOneFile(const char* filename)
{
if (m_ignoreExt && Util::MatchFileExt(FileSystem::BaseFileName(filename), m_ignoreExt, ",;"))
{
return;
}
RarVolume volume(filename);
volume.SetPassword(m_password);
if (volume.Read())
{
m_volumes.push_back(std::move(volume));
}
}
void RarRenamer::RenameFile(const char* srcFilename, const char* destFileName)
{
PrintMessage(Message::mkInfo, "Renaming %s to %s", FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
if (!FileSystem::MoveFile(srcFilename, destFileName))
{
PrintMessage(Message::mkError, "Could not rename %s to %s: %s", srcFilename, destFileName,
*FileSystem::GetLastErrorMessage());
return;
}
m_renamedCount++;
// notify about new file name
RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
}
void RarRenamer::RenameFiles(const char* destDir)
{
MakeSets();
for (RarVolumeSet& set : m_sets)
{
if (!IsSetProperlyNamed(set))
{
RarFile* mainFile = FindMainFile(set);
BString<1024> mainBasename = FileSystem::BaseFileName(mainFile->GetFilename());
char* ext = strrchr(mainBasename, '.');
// strip extension if its length is 3 chars
if (ext && strlen(ext) == 4)
{
*ext = '\0';
}
BString<1024> newBasename = *mainBasename;
int num = 0;
bool willOverwrite = true;
while (willOverwrite)
{
if (num++)
{
newBasename.Format("%s-%i", *mainBasename, num);
}
for (RarVolume* volume : set)
{
CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
willOverwrite = strcmp(volume->GetFilename(), destfilename) && FileSystem::FileExists(destfilename);
if (willOverwrite)
{
break;
}
}
}
for (RarVolume* volume : set)
{
CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
if (strcmp(volume->GetFilename(), destfilename))
{
RenameFile(volume->GetFilename(), destfilename);
}
}
}
}
}
CString RarRenamer::GenNewVolumeFilename(const char* destDir, const char* newBasename, RarVolume* volume)
{
CString extension = volume->GetNewNaming() ? GenNewExtension(volume->GetVolumeNo()) : GenOldExtension(volume->GetVolumeNo());
return CString::FormatStr("%s%c%s.%s", destDir, PATH_SEPARATOR, newBasename, *extension);
}
CString RarRenamer::GenNewExtension(int volumeNo)
{
return CString::FormatStr("part%04i.rar", volumeNo + 1);
}
CString RarRenamer::GenOldExtension(int volumeNo)
{
if (volumeNo == 0)
{
return "rar";
}
else
{
unsigned char ch = 'r' + (volumeNo - 1) / 100;
return CString::FormatStr("%c%02d", ch, (volumeNo - 1) % 100);
}
}
void RarRenamer::MakeSets()
{
m_sets.clear();
// find first volumes and create initial incomplete sets
for (RarVolume& volume : m_volumes)
{
if (!volume.GetFiles()->empty() && volume.GetVolumeNo() == 0)
{
m_sets.push_back({&volume});
}
}
// complete sets, discard sets which cannot be completed
m_sets.erase(std::remove_if(m_sets.begin(), m_sets.end(),
[volumes = &m_volumes](RarVolumeSet& set)
{
debug("*** Building set %s", FileSystem::BaseFileName(set[0]->GetFilename()));
bool found = true;
while (found)
{
found = false;
RarVolume* lastVolume = set.back();
for (RarVolume& volume : *volumes)
{
if (!volume.GetFiles()->empty() && volume.GetMultiVolume() &&
volume.GetVolumeNo() == lastVolume->GetVolumeNo() + 1 &&
volume.GetVersion() == lastVolume->GetVersion() &&
lastVolume->GetHasNextVolume() &&
((volume.GetFiles()->at(0).GetSplitBefore() &&
lastVolume->GetFiles()->at(0).GetSplitAfter() &&
!strcmp(volume.GetFiles()->at(0).GetFilename(), lastVolume->GetFiles()->at(0).GetFilename())) ||
(!volume.GetFiles()->at(0).GetSplitBefore() && !lastVolume->GetFiles()->at(0).GetSplitAfter())))
{
debug(" adding %s", FileSystem::BaseFileName(volume.GetFilename()));
set.push_back(&volume);
found = true;
break;
}
}
}
bool completed = !set.back()->GetHasNextVolume();
return !completed;
}),
m_sets.end());
// debug log
for (RarVolumeSet& set : m_sets)
{
debug("*** Set ***");
for (RarVolume* volume : set)
{
debug(" %s", FileSystem::BaseFileName(volume->GetFilename()));
}
}
}
bool RarRenamer::IsSetProperlyNamed(RarVolumeSet& set)
{
RegEx regExPart(".*.part([0-9]+)\\.rar$");
const char* setBasename = FileSystem::BaseFileName(set[0]->GetFilename());
int setPartLen = 0;
for (RarVolume* volume : set)
{
const char* filename = FileSystem::BaseFileName(volume->GetFilename());
if (strlen(setBasename) != strlen(filename))
{
return false;
}
if (volume->GetNewNaming())
{
if (!regExPart.Match(filename))
{
return false;
}
BString<1024> partNo(filename + regExPart.GetMatchStart(1), regExPart.GetMatchLen(1));
if (setPartLen == 0)
{
setPartLen = partNo.Length();
}
bool ok = atoi(partNo) == volume->GetVolumeNo() + 1 &&
partNo.Length() == setPartLen &&
!strncmp(setBasename, filename, regExPart.GetMatchStart(1));
if (!ok)
{
return false;
}
}
else
{
const char* ext = strrchr(filename, '.');
if (!ext || strcmp(ext + 1, GenOldExtension(volume->GetVolumeNo())) ||
strncmp(setBasename, filename, ext - filename))
{
return false;
}
}
}
return true;
}
RarFile* RarRenamer::FindMainFile(RarVolumeSet& set)
{
std::deque<RarFile*> allFiles;
for (RarVolume* volume : set)
{
for (RarFile& file : *volume->GetFiles())
{
allFiles.push_back(&file);
}
}
std::deque<RarFile*>::iterator it = std::max_element(allFiles.begin(), allFiles.end(),
[](RarFile* file1, RarFile* file2)
{
return file1->GetSize() < file2->GetSize();
});
return *it;
}

View File

@@ -0,0 +1,81 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RARRENAMER_H
#define RARRENAMER_H
#include "NString.h"
#include "Log.h"
#include "FileSystem.h"
#include "RarReader.h"
class RarRenamer
{
public:
void Execute();
void SetDestDir(const char* destDir) { m_destDir = destDir; }
const char* GetInfoName() { return m_infoName; }
void SetInfoName(const char* infoName) { m_infoName = infoName; }
void SetPassword(const char* password) { m_password = password; }
void SetIgnoreExt(const char* ignoreExt) { m_ignoreExt = ignoreExt; }
int GetRenamedCount() { return m_renamedCount; }
protected:
virtual void UpdateProgress() {}
virtual bool IsStopped() { return false; };
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName) {}
const char* GetProgressLabel() { return m_progressLabel; }
int GetStageProgress() { return m_stageProgress; }
private:
typedef std::deque<CString> DirList;
typedef std::deque<RarVolume> RarVolumeList;
typedef std::deque<RarVolume*> RarVolumeSet;
typedef std::deque<RarVolumeSet> RarSets;
CString m_infoName;
CString m_destDir;
CString m_progressLabel;
int m_stageProgress = 0;
bool m_cancelled = false;
DirList m_dirList;
int m_fileCount = 0;
int m_curFile = 0;
int m_renamedCount = 0;
RarVolumeList m_volumes;
RarSets m_sets;
CString m_password;
CString m_ignoreExt;
void BuildDirList(const char* destDir);
void CheckFiles(const char* destDir);
void CheckOneFile(const char* filename);
void RenameFile(const char* srcFilename, const char* destFileName);
void RenameFiles(const char* destDir);
CString GenNewVolumeFilename(const char* destDir, const char* newBasename, RarVolume* volume);
CString GenNewExtension(int volumeNo);
CString GenOldExtension(int volumeNo);
void MakeSets();
bool IsSetProperlyNamed(RarVolumeSet& set);
RarFile* FindMainFile(RarVolumeSet& set);
};
#endif

View File

@@ -0,0 +1,221 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "Options.h"
#include "DiskState.h"
#include "Log.h"
#include "FileSystem.h"
#include "Rename.h"
#ifndef DISABLE_PARCHECK
void RenameController::PostParRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
{
char text[1024];
va_list args;
va_start(args, format);
vsnprintf(text, 1024, format, args);
va_end(args);
text[1024-1] = '\0';
m_owner->m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
#endif
void RenameController::PostRarRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
{
char text[1024];
va_list args;
va_start(args, format);
vsnprintf(text, 1024, format, args);
va_end(args);
text[1024 - 1] = '\0';
m_owner->m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
RenameController::RenameController()
{
debug("Creating RenameController");
#ifndef DISABLE_PARCHECK
m_parRenamer.m_owner = this;
#endif
m_rarRenamer.m_owner = this;
}
void RenameController::StartJob(PostInfo* postInfo, EJobKind kind)
{
RenameController* renameController = new RenameController();
renameController->m_postInfo = postInfo;
renameController->m_kind = kind;
renameController->SetAutoDestroy(false);
postInfo->SetPostThread(renameController);
renameController->Start();
}
void RenameController::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("rename for %s", *nzbName);
SetInfoName(infoName);
PrintMessage(Message::mkInfo, "Checking renamed %sfiles for %s",
m_kind == jkRar ? "archive " : "", *nzbName);
ExecRename(destDir, finalDir, nzbName);
if (IsStopped())
{
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", *nzbName);
}
else if (m_renamedCount > 0)
{
PrintMessage(Message::mkInfo, "Successfully renamed %i %sfile(s) for %s",
m_renamedCount, m_kind == jkRar ? "archive " : "", *nzbName);
}
else
{
PrintMessage(Message::mkInfo, "No renamed %sfiles found for %s",
m_kind == jkRar ? "archive " : "", *nzbName);
}
RenameCompleted();
}
void RenameController::AddMessage(Message::EKind kind, const char* text)
{
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
void RenameController::ExecRename(const char* destDir, const char* finalDir, const char* nzbName)
{
if (m_kind == jkPar)
{
#ifndef DISABLE_PARCHECK
m_parRenamer.SetDestDir(m_postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usSuccess &&
!Util::EmptyStr(finalDir) ? finalDir : destDir);
m_parRenamer.SetInfoName(nzbName);
m_parRenamer.SetDetectMissing(m_postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone);
m_parRenamer.Execute();
#endif
}
else if (m_kind == jkRar)
{
m_rarRenamer.SetDestDir(destDir);
m_rarRenamer.SetInfoName(nzbName);
m_rarRenamer.SetIgnoreExt(g_Options->GetUnpackIgnoreExt());
NzbParameter* parameter = m_postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:Password", false);
if (parameter)
{
m_rarRenamer.SetPassword(parameter->GetValue());
}
m_rarRenamer.Execute();
}
}
void RenameController::RenameCompleted()
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (m_kind == jkPar)
{
m_postInfo->GetNzbInfo()->SetParRenameStatus(m_renamedCount > 0 ? NzbInfo::rsSuccess : NzbInfo::rsNothing);
#ifndef DISABLE_PARCHECK
// request another par2-file if the renaming has failed due to damaged par2-files
if (m_renamedCount == 0 && m_parRenamer.HasDamagedParFiles() &&
m_postInfo->GetNzbInfo()->GetRemainingParCount() > 0)
{
m_parRenamer.PrintMessage(Message::mkInfo, "Requesting extra par2-files for %s to perform par-rename", m_parRenamer.GetInfoName());
downloadQueue->EditEntry(m_postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, nullptr);
downloadQueue->EditEntry(m_postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPauseExtraPars, nullptr);
if (m_postInfo->GetNzbInfo()->GetRemainingSize() > 0)
{
// reset rename status to execute renamer again, after the new par2-file is downloaded
m_postInfo->GetNzbInfo()->SetParRenameStatus(NzbInfo::rsNone);
}
}
#endif
}
else if (m_kind == jkRar)
{
m_postInfo->GetNzbInfo()->SetRarRenameStatus(m_renamedCount > 0 ? NzbInfo::rsSuccess : NzbInfo::rsNothing);
}
#ifndef DISABLE_PARCHECK
if (m_parRenamer.HasMissedFiles() && m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
{
m_parRenamer.PrintMessage(Message::mkInfo, "Requesting par-check/repair for %s to restore missing files ", m_parRenamer.GetInfoName());
m_postInfo->SetRequestParCheck(true);
}
#endif
m_postInfo->SetWorking(false);
}
#ifndef DISABLE_PARCHECK
void RenameController::UpdateParRenameProgress()
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(m_parRenamer.GetProgressLabel());
m_postInfo->SetStageProgress(m_parRenamer.GetStageProgress());
}
#endif
void RenameController::UpdateRarRenameProgress()
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(m_rarRenamer.GetProgressLabel());
m_postInfo->SetStageProgress(m_rarRenamer.GetStageProgress());
}
/**
* Update file name in the CompletedFiles-list of NZBInfo
*/
void RenameController::RegisterRenamedFile(const char* oldFilename, const char* newFilename)
{
for (CompletedFile& completedFile : m_postInfo->GetNzbInfo()->GetCompletedFiles())
{
if (!strcasecmp(completedFile.GetFilename(), oldFilename))
{
completedFile.SetFilename(newFilename);
break;
}
}
m_renamedCount++;
}

View File

@@ -0,0 +1,98 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RENAME_H
#define RENAME_H
#include "Thread.h"
#include "DownloadInfo.h"
#include "Script.h"
#include "RarRenamer.h"
#ifndef DISABLE_PARCHECK
#include "ParRenamer.h"
#endif
class RenameController : public Thread, public ScriptController
{
public:
enum EJobKind
{
jkPar,
jkRar
};
RenameController();
virtual void Run();
static void StartJob(PostInfo* postInfo, EJobKind kind);
protected:
virtual void AddMessage(Message::EKind kind, const char* text);
private:
PostInfo* m_postInfo;
CString m_destDir;
int m_renamedCount = 0;
EJobKind m_kind;
#ifndef DISABLE_PARCHECK
class PostParRenamer : public ParRenamer
{
protected:
virtual void UpdateProgress() { m_owner->UpdateParRenameProgress(); }
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
virtual void RegisterParredFile(const char* filename)
{ m_owner->m_postInfo->GetParredFiles()->push_back(filename); }
virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName)
{ m_owner->RegisterRenamedFile(oldFilename, newFileName); }
virtual bool IsStopped() { return m_owner->IsStopped(); };
private:
RenameController* m_owner;
friend class RenameController;
};
PostParRenamer m_parRenamer;
void UpdateParRenameProgress();
#endif
class PostRarRenamer : public RarRenamer
{
protected:
virtual void UpdateProgress() { m_owner->UpdateRarRenameProgress(); }
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
virtual void RegisterRenamedFile(const char* oldFilename, const char* newFilename)
{ m_owner->RegisterRenamedFile(oldFilename, newFilename); }
virtual bool IsStopped() { return m_owner->IsStopped(); };
private:
RenameController* m_owner;
friend class RenameController;
};
PostRarRenamer m_rarRenamer;
void UpdateRarRenameProgress();
void ExecRename(const char* destDir, const char* finalDir, const char* nzbName);
void RenameCompleted();
void RegisterRenamedFile(const char* oldFilename, const char* newFilename);
};
#endif

View File

@@ -19,7 +19,7 @@
#include "nzbget.h"
#include "ParCoordinator.h"
#include "Repair.h"
#include "DupeCoordinator.h"
#include "ParParser.h"
#include "Options.h"
@@ -28,17 +28,17 @@
#include "FileSystem.h"
#ifndef DISABLE_PARCHECK
bool ParCoordinator::PostParChecker::RequestMorePars(int blockNeeded, int* blockFound)
bool RepairController::PostParChecker::RequestMorePars(int blockNeeded, int* blockFound)
{
return m_owner->RequestMorePars(m_postInfo->GetNzbInfo(), GetParFilename(), blockNeeded, blockFound);
}
void ParCoordinator::PostParChecker::UpdateProgress()
void RepairController::PostParChecker::UpdateProgress()
{
m_owner->UpdateParCheckProgress();
}
void ParCoordinator::PostParChecker::PrintMessage(Message::EKind kind, const char* format, ...)
void RepairController::PostParChecker::PrintMessage(Message::EKind kind, const char* format, ...)
{
char text[1024];
va_list args;
@@ -50,12 +50,12 @@ void ParCoordinator::PostParChecker::PrintMessage(Message::EKind kind, const cha
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
void ParCoordinator::PostParChecker::RegisterParredFile(const char* filename)
void RepairController::PostParChecker::RegisterParredFile(const char* filename)
{
m_postInfo->GetParredFiles()->push_back(filename);
}
bool ParCoordinator::PostParChecker::IsParredFile(const char* filename)
bool RepairController::PostParChecker::IsParredFile(const char* filename)
{
for (CString& parredFile : m_postInfo->GetParredFiles())
{
@@ -67,14 +67,14 @@ bool ParCoordinator::PostParChecker::IsParredFile(const char* filename)
return false;
}
ParChecker::EFileStatus ParCoordinator::PostParChecker::FindFileCrc(const char* filename,
ParChecker::EFileStatus RepairController::PostParChecker::FindFileCrc(const char* filename,
uint32* crc, SegmentList* segments)
{
CompletedFile* completedFile = nullptr;
for (CompletedFile& completedFile2 : m_postInfo->GetNzbInfo()->GetCompletedFiles())
{
if (!strcasecmp(completedFile2.GetFileName(), filename))
if (!strcasecmp(completedFile2.GetFilename(), filename))
{
completedFile = &completedFile2;
break;
@@ -85,7 +85,7 @@ ParChecker::EFileStatus ParCoordinator::PostParChecker::FindFileCrc(const char*
return ParChecker::fsUnknown;
}
debug("Found completed file: %s, CRC: %.8x, Status: %i", FileSystem::BaseFileName(completedFile->GetFileName()), completedFile->GetCrc(), (int)completedFile->GetStatus());
debug("Found completed file: %s, CRC: %.8x, Status: %i", FileSystem::BaseFileName(completedFile->GetFilename()), completedFile->GetCrc(), (int)completedFile->GetStatus());
*crc = completedFile->GetCrc();
@@ -114,7 +114,7 @@ ParChecker::EFileStatus ParCoordinator::PostParChecker::FindFileCrc(const char*
ParChecker::fsUnknown;
}
void ParCoordinator::PostParChecker::RequestDupeSources(DupeSourceList* dupeSourceList)
void RepairController::PostParChecker::RequestDupeSources(DupeSourceList* dupeSourceList)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
@@ -145,7 +145,7 @@ void ParCoordinator::PostParChecker::RequestDupeSources(DupeSourceList* dupeSour
}
}
void ParCoordinator::PostParChecker::StatDupeSources(DupeSourceList* dupeSourceList)
void RepairController::PostParChecker::StatDupeSources(DupeSourceList* dupeSourceList)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
@@ -169,44 +169,8 @@ void ParCoordinator::PostParChecker::StatDupeSources(DupeSourceList* dupeSourceL
m_postInfo->GetNzbInfo()->SetExtraParBlocks(m_postInfo->GetNzbInfo()->GetExtraParBlocks() + totalExtraParBlocks);
}
void ParCoordinator::PostParRenamer::UpdateProgress()
{
m_owner->UpdateParRenameProgress();
}
void ParCoordinator::PostParRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
{
char text[1024];
va_list args;
va_start(args, format);
vsnprintf(text, 1024, format, args);
va_end(args);
text[1024-1] = '\0';
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
void ParCoordinator::PostParRenamer::RegisterParredFile(const char* filename)
{
m_postInfo->GetParredFiles()->push_back(filename);
}
/**
* Update file name in the CompletedFiles-list of NZBInfo
*/
void ParCoordinator::PostParRenamer::RegisterRenamedFile(const char* oldFilename, const char* newFileName)
{
for (CompletedFile& completedFile : m_postInfo->GetNzbInfo()->GetCompletedFiles())
{
if (!strcasecmp(completedFile.GetFileName(), oldFilename))
{
completedFile.SetFileName(newFileName);
break;
}
}
}
void ParCoordinator::PostDupeMatcher::PrintMessage(Message::EKind kind, const char* format, ...)
void RepairController::PostDupeMatcher::PrintMessage(Message::EKind kind, const char* format, ...)
{
char text[1024];
va_list args;
@@ -220,130 +184,69 @@ void ParCoordinator::PostDupeMatcher::PrintMessage(Message::EKind kind, const ch
#endif
ParCoordinator::ParCoordinator()
RepairController::RepairController()
{
debug("Creating ParCoordinator");
debug("Creating RepairController");
#ifndef DISABLE_PARCHECK
m_parChecker.m_owner = this;
m_parRenamer.m_owner = this;
#endif
}
ParCoordinator::~ParCoordinator()
void RepairController::Stop()
{
debug("Destroying ParCoordinator");
debug("Stopping RepairController");
Thread::Stop();
#ifndef DISABLE_PARCHECK
m_parChecker.Cancel();
#endif
}
#ifndef DISABLE_PARCHECK
void ParCoordinator::Stop()
void RepairController::StartJob(PostInfo* postInfo)
{
debug("Stopping ParCoordinator");
RepairController* repairController = new RepairController();
repairController->m_postInfo = postInfo;
repairController->SetAutoDestroy(false);
m_stopped = true;
postInfo->SetPostThread(repairController);
if (m_parChecker.IsRunning())
repairController->Start();
}
void RepairController::Run()
{
BString<1024> nzbName;
CString destDir;
{
m_parChecker.Stop();
int mSecWait = 5000;
while (m_parChecker.IsRunning() && mSecWait > 0)
{
usleep(50 * 1000);
mSecWait -= 50;
}
if (m_parChecker.IsRunning())
{
warn("Terminating par-check for %s", m_parChecker.GetInfoName());
m_parChecker.Kill();
}
GuardedDownloadQueue guard = DownloadQueue::Guard();
nzbName = m_postInfo->GetNzbInfo()->GetName();
destDir = m_postInfo->GetNzbInfo()->GetDestDir();
}
}
#endif
void ParCoordinator::PausePars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
debug("ParCoordinator: Pausing pars");
downloadQueue->EditEntry(nzbInfo->GetId(),
DownloadQueue::eaGroupPauseExtraPars, 0, nullptr);
}
#ifndef DISABLE_PARCHECK
/**
* DownloadQueue must be locked prior to call of this function.
*/
void ParCoordinator::StartParCheckJob(PostInfo* postInfo)
{
m_currentJob = jkParCheck;
m_parChecker.SetPostInfo(postInfo);
m_parChecker.SetDestDir(postInfo->GetNzbInfo()->GetDestDir());
m_parChecker.SetNzbName(postInfo->GetNzbInfo()->GetName());
m_parChecker.SetPostInfo(m_postInfo);
m_parChecker.SetDestDir(destDir);
m_parChecker.SetNzbName(nzbName);
m_parChecker.SetParTime(Util::CurrentTime());
m_parChecker.SetDownloadSec(postInfo->GetNzbInfo()->GetDownloadSec());
m_parChecker.SetParQuick(g_Options->GetParQuick() && !postInfo->GetForceParFull());
m_parChecker.SetForceRepair(postInfo->GetForceRepair());
m_parChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", postInfo->GetNzbInfo()->GetName());
postInfo->SetWorking(true);
m_parChecker.Start();
m_parChecker.SetDownloadSec(m_postInfo->GetNzbInfo()->GetDownloadSec());
m_parChecker.SetParQuick(g_Options->GetParQuick() && !m_postInfo->GetForceParFull());
m_parChecker.SetForceRepair(m_postInfo->GetForceRepair());
m_parChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", *nzbName);
m_parChecker.Execute();
}
/**
* DownloadQueue must be locked prior to call of this function.
*/
void ParCoordinator::StartParRenameJob(PostInfo* postInfo)
bool RepairController::AddPar(FileInfo* fileInfo, bool deleted)
{
const char* destDir = postInfo->GetNzbInfo()->GetDestDir();
if (postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usSuccess &&
!Util::EmptyStr(postInfo->GetNzbInfo()->GetFinalDir()))
{
destDir = postInfo->GetNzbInfo()->GetFinalDir();
}
m_currentJob = jkParRename;
m_parRenamer.SetPostInfo(postInfo);
m_parRenamer.SetDestDir(destDir);
m_parRenamer.SetInfoName(postInfo->GetNzbInfo()->GetName());
m_parRenamer.SetDetectMissing(postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone);
m_parRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", postInfo->GetNzbInfo()->GetName());
postInfo->SetWorking(true);
m_parRenamer.Start();
}
bool ParCoordinator::Cancel()
{
if (m_currentJob == jkParCheck)
{
if (!m_parChecker.GetCancelled())
{
debug("Cancelling par-repair for %s", m_parChecker.GetInfoName());
m_parChecker.Cancel();
return true;
}
}
else if (m_currentJob == jkParRename)
{
if (!m_parRenamer.GetCancelled())
{
debug("Cancelling par-rename for %s", m_parRenamer.GetInfoName());
m_parRenamer.Cancel();
return true;
}
}
return false;
}
/**
* DownloadQueue must be locked prior to call of this function.
*/
bool ParCoordinator::AddPar(FileInfo* fileInfo, bool deleted)
{
bool sameCollection = m_parChecker.IsRunning() &&
fileInfo->GetNzbInfo() == m_parChecker.GetPostInfo()->GetNzbInfo();
bool sameCollection = fileInfo->GetNzbInfo() == m_parChecker.GetPostInfo()->GetNzbInfo();
if (sameCollection && !deleted)
{
BString<1024> fullFilename("%s%c%s", fileInfo->GetNzbInfo()->GetDestDir(), (int)PATH_SEPARATOR, fileInfo->GetFilename());
BString<1024> fullFilename("%s%c%s", fileInfo->GetNzbInfo()->GetDestDir(), PATH_SEPARATOR, fileInfo->GetFilename());
m_parChecker.AddParFile(fullFilename);
}
else
@@ -353,7 +256,7 @@ bool ParCoordinator::AddPar(FileInfo* fileInfo, bool deleted)
return sameCollection;
}
void ParCoordinator::ParCheckCompleted()
void RepairController::ParCheckCompleted()
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
@@ -385,9 +288,6 @@ void ParCoordinator::ParCheckCompleted()
postInfo->GetNzbInfo()->SetParFull(m_parChecker.GetParFull());
postInfo->SetWorking(false);
postInfo->SetStage(PostInfo::ptQueued);
downloadQueue->Save();
}
/**
@@ -397,80 +297,68 @@ void ParCoordinator::ParCheckCompleted()
* special case: returns true if there are any unpaused par2-files in the queue regardless
* of the amount of blocks; this is to keep par-checker wait for download completion.
*/
bool ParCoordinator::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFoundOut)
bool RepairController::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFoundOut)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
Blocks blocks;
blocks.clear();
Blocks availableBlocks;
Blocks selectedBlocks;
int blockFound = 0;
int curBlockFound = 0;
FindPars(downloadQueue, nzbInfo, parFilename, blocks, true, true, &curBlockFound);
FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, true, true, &curBlockFound);
blockFound += curBlockFound;
if (blockFound < blockNeeded)
{
FindPars(downloadQueue, nzbInfo, parFilename, blocks, true, false, &curBlockFound);
FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, true, false, &curBlockFound);
blockFound += curBlockFound;
}
if (blockFound < blockNeeded)
{
FindPars(downloadQueue, nzbInfo, parFilename, blocks, false, false, &curBlockFound);
FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, false, false, &curBlockFound);
blockFound += curBlockFound;
}
std::sort(availableBlocks.begin(), availableBlocks.end(),
[](const BlockInfo& block1, const BlockInfo& block2)
{
return block1.m_blockCount < block2.m_blockCount;
});
if (blockFound >= blockNeeded)
{
// 1. first unpause all files with par-blocks less or equal iBlockNeeded
// starting from the file with max block count.
// if par-collection was built exponentially and all par-files present,
// this step selects par-files with exact number of blocks we need.
while (blockNeeded > 0)
// collect as much blocks as needed
for (Blocks::iterator it = availableBlocks.begin(); blockNeeded > 0 && it != availableBlocks.end(); it++)
{
BlockInfo* bestBlockInfo = nullptr;
Blocks::iterator bestBlockIter;
for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
BlockInfo& blockInfo = *it;
selectedBlocks.push_front(blockInfo);
blockNeeded -= blockInfo.m_blockCount;
}
// discarding superfluous blocks
for (Blocks::iterator it = selectedBlocks.begin(); it != selectedBlocks.end(); )
{
BlockInfo& blockInfo = *it;
if (blockNeeded + blockInfo.m_blockCount <= 0)
{
BlockInfo& blockInfo = *it;
if (blockInfo.m_blockCount <= blockNeeded &&
(!bestBlockInfo || bestBlockInfo->m_blockCount < blockInfo.m_blockCount))
{
bestBlockInfo = &blockInfo;
bestBlockIter = it;
}
}
if (bestBlockInfo)
{
if (bestBlockInfo->m_fileInfo->GetPaused())
{
m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), (int)PATH_SEPARATOR, bestBlockInfo->m_fileInfo->GetFilename());
bestBlockInfo->m_fileInfo->SetPaused(false);
bestBlockInfo->m_fileInfo->SetExtraPriority(true);
}
blockNeeded -= bestBlockInfo->m_blockCount;
blocks.erase(bestBlockIter);
blockNeeded += blockInfo.m_blockCount;
it = selectedBlocks.erase(it);
}
else
{
break;
it++;
}
}
// 2. then unpause other files
// this step only needed if the par-collection was built not exponentially
// or not all par-files present (or some of them were corrupted)
// this step is not optimal, but we hope, that the first step will work good
// in most cases and we will not need the second step often
while (blockNeeded > 0)
// unpause files with blocks
for (BlockInfo& blockInfo : selectedBlocks)
{
BlockInfo& blockInfo = blocks.front();
if (blockInfo.m_fileInfo->GetPaused())
{
m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), (int)PATH_SEPARATOR, blockInfo.m_fileInfo->GetFilename());
m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), PATH_SEPARATOR, blockInfo.m_fileInfo->GetFilename());
blockInfo.m_fileInfo->SetPaused(false);
blockInfo.m_fileInfo->SetExtraPriority(true);
}
blockNeeded -= blockInfo.m_blockCount;
}
}
@@ -494,7 +382,7 @@ bool ParCoordinator::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename,
return ok;
}
void ParCoordinator::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
void RepairController::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
Blocks& blocks, bool strictParName, bool exactParName, int* blockFound)
{
*blockFound = 0;
@@ -502,7 +390,7 @@ void ParCoordinator::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, co
// extract base name from m_szParFilename (trim .par2-extension and possible .vol-part)
char* baseParFilename = FileSystem::BaseFileName(parFilename);
int mainBaseLen = 0;
if (!ParParser::ParseParFilename(baseParFilename, &mainBaseLen, nullptr))
if (!ParParser::ParseParFilename(baseParFilename, true, &mainBaseLen, nullptr))
{
// should not happen
nzbInfo->PrintMessage(Message::mkError, "Internal error: could not parse filename %s", baseParFilename);
@@ -515,14 +403,15 @@ void ParCoordinator::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, co
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
int blockCount = 0;
if (ParParser::ParseParFilename(fileInfo->GetFilename(), nullptr, &blockCount) &&
if (ParParser::ParseParFilename(fileInfo->GetFilename(), fileInfo->GetFilenameConfirmed(), nullptr, &blockCount) &&
blockCount > 0)
{
bool useFile = true;
if (exactParName)
{
useFile = ParParser::SameParCollection(fileInfo->GetFilename(), FileSystem::BaseFileName(parFilename));
useFile = ParParser::SameParCollection(fileInfo->GetFilename(),
FileSystem::BaseFileName(parFilename), fileInfo->GetFilenameConfirmed());
}
else if (strictParName)
{
@@ -566,7 +455,7 @@ void ParCoordinator::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, co
}
}
void ParCoordinator::UpdateParCheckProgress()
void RepairController::UpdateParCheckProgress()
{
PostInfo* postInfo;
@@ -600,7 +489,7 @@ void ParCoordinator::UpdateParCheckProgress()
}
bool parCancel = false;
if (!m_parChecker.GetCancelled())
if (!IsStopped())
{
if ((g_Options->GetParTimeLimit() > 0) &&
m_parChecker.GetStage() == PostParChecker::ptRepairing &&
@@ -621,14 +510,14 @@ void ParCoordinator::UpdateParCheckProgress()
if (parCancel)
{
m_parChecker.Cancel();
Stop();
}
}
CheckPauseState(postInfo);
}
void ParCoordinator::CheckPauseState(PostInfo* postInfo)
void RepairController::CheckPauseState(PostInfo* postInfo)
{
if (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority())
{
@@ -639,7 +528,7 @@ void ParCoordinator::CheckPauseState(PostInfo* postInfo)
time_t waitTime = Util::CurrentTime();
// wait until Post-processor is unpaused
while (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority() && !m_stopped)
while (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
{
usleep(50 * 1000);
@@ -667,44 +556,4 @@ void ParCoordinator::CheckPauseState(PostInfo* postInfo)
}
}
void ParCoordinator::ParRenameCompleted()
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
PostInfo* postInfo = m_parRenamer.GetPostInfo();
postInfo->GetNzbInfo()->SetRenameStatus(m_parRenamer.GetStatus() == ParRenamer::psSuccess ? NzbInfo::rsSuccess : NzbInfo::rsFailure);
if (m_parRenamer.HasMissedFiles() && postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
{
m_parRenamer.PrintMessage(Message::mkInfo, "Requesting par-check/repair for %s to restore missing files ", m_parRenamer.GetInfoName());
postInfo->SetRequestParCheck(true);
}
postInfo->SetWorking(false);
postInfo->SetStage(PostInfo::ptQueued);
downloadQueue->Save();
}
void ParCoordinator::UpdateParRenameProgress()
{
PostInfo* postInfo;
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
postInfo = m_parRenamer.GetPostInfo();
postInfo->SetProgressLabel(m_parRenamer.GetProgressLabel());
postInfo->SetStageProgress(m_parRenamer.GetStageProgress());
time_t current = Util::CurrentTime();
if (postInfo->GetStage() != PostInfo::ptRenaming)
{
postInfo->SetStage(PostInfo::ptRenaming);
postInfo->SetStageTime(current);
}
}
CheckPauseState(postInfo);
}
#endif

View File

@@ -18,36 +18,32 @@
*/
#ifndef PARCOORDINATOR_H
#define PARCOORDINATOR_H
#ifndef REPAIR_H
#define REPAIR_H
#include "DownloadInfo.h"
#include "Thread.h"
#include "Script.h"
#ifndef DISABLE_PARCHECK
#include "ParChecker.h"
#include "ParRenamer.h"
#include "DupeMatcher.h"
#endif
class ParCoordinator
class RepairController : public Thread, public ScriptController
{
public:
ParCoordinator();
virtual ~ParCoordinator();
void PausePars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
RepairController();
virtual void Stop();
#ifndef DISABLE_PARCHECK
virtual void Run();
static void StartJob(PostInfo* postInfo);
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);
@@ -66,6 +62,7 @@ private:
protected:
virtual bool RequestMorePars(int blockNeeded, int* blockFound);
virtual void UpdateProgress();
virtual bool IsStopped() { return m_owner->IsStopped(); };
virtual void Completed() { m_owner->ParCheckCompleted(); }
virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
virtual void RegisterParredFile(const char* filename);
@@ -74,31 +71,13 @@ private:
virtual void RequestDupeSources(DupeSourceList* dupeSourceList);
virtual void StatDupeSources(DupeSourceList* dupeSourceList);
private:
ParCoordinator* m_owner;
RepairController* m_owner;
PostInfo* m_postInfo;
time_t m_parTime;
time_t m_repairTime;
int m_downloadSec;
friend class ParCoordinator;
};
class PostParRenamer: public ParRenamer
{
public:
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;
friend class RepairController;
};
class PostDupeMatcher: public DupeMatcher
@@ -124,16 +103,8 @@ private:
typedef std::deque<BlockInfo> Blocks;
enum EJobKind
{
jkParCheck,
jkParRename
};
PostInfo* m_postInfo;
PostParChecker m_parChecker;
bool m_stopped = false;
PostParRenamer m_parRenamer;
EJobKind m_currentJob;
void FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
Blocks& blocks, bool strictParName, bool exactParName, int* blockFound);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -50,17 +50,7 @@ void UnpackController::StartJob(PostInfo* postInfo)
void UnpackController::Run()
{
time_t start = Util::CurrentTime();
m_cleanedUpDisk = false;
m_finalDirCreated = false;
m_unpackOk = true;
m_unpackStartError = false;
m_unpackSpaceError = false;
m_unpackDecryptError = false;
m_unpackPasswordError = false;
m_autoTerminated = false;
m_passListTried = false;
bool unpack;
{
@@ -87,16 +77,13 @@ void UnpackController::Run()
if (unpack)
{
bool scanNonStdFiles = m_postInfo->GetNzbInfo()->GetRenameStatus() > NzbInfo::rsSkipped ||
m_postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSuccess ||
!m_hasParFiles;
CheckArchiveFiles(scanNonStdFiles);
CheckArchiveFiles();
}
SetInfoName(m_infoName);
SetWorkingDir(m_destDir);
bool hasFiles = m_hasRarFiles || m_hasNonStdRarFiles || m_hasSevenZipFiles || m_hasSevenZipMultiFiles || m_hasSplittedFiles;
bool hasFiles = m_hasRarFiles || m_hasSevenZipFiles || m_hasSevenZipMultiFiles || m_hasSplittedFiles;
if (m_postInfo->GetUnpackTried() && !m_postInfo->GetParRepaired() &&
(!m_password.Empty() || Util::EmptyStr(g_Options->GetUnpackPassFile()) || m_postInfo->GetPassListTried()))
@@ -106,8 +93,7 @@ void UnpackController::Run()
m_postInfo->GetLastUnpackStatus() == (int)NzbInfo::usPassword ?
"%s failed: checksum error in the encrypted file. Corrupt file or wrong password." : "%s failed.",
*m_infoNameUp);
m_postInfo->GetNzbInfo()->SetUnpackStatus((NzbInfo::EUnpackStatus)m_postInfo->GetLastUnpackStatus());
m_postInfo->SetStage(PostInfo::ptQueued);
m_postInfo->GetNzbInfo()->SetUnpackStatus((NzbInfo::EPostUnpackStatus)m_postInfo->GetLastUnpackStatus());
}
else if (unpack && hasFiles)
{
@@ -115,9 +101,20 @@ void UnpackController::Run()
CreateUnpackDir();
if (m_hasRarFiles || m_hasNonStdRarFiles)
if (m_hasRarFiles)
{
UnpackArchives(upUnrar, false);
if (m_hasUnpackedRarFiles)
{
if (m_postInfo->GetNzbInfo()->GetDirectUnpackStatus() == NzbInfo::nsSuccess)
{
PrintMessage(Message::mkInfo, "Found archive files not processed by direct unpack, unpacking all files again");
}
UnpackArchives(upUnrar, false);
}
else
{
PrintMessage(Message::mkInfo, "Using directly unpacked files");
}
}
if (m_hasSevenZipFiles && m_unpackOk)
@@ -139,23 +136,35 @@ void UnpackController::Run()
m_joinedFiles.clear();
}
else
else if (unpack)
{
PrintMessage(Message::mkInfo, (unpack ? "Nothing to unpack for %s" : "Unpack for %s skipped"), *m_name);
#ifndef DISABLE_PARCHECK
if (unpack && m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
m_postInfo->GetNzbInfo()->GetRenameStatus() <= NzbInfo::rsSkipped && m_hasParFiles)
if (m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
m_postInfo->GetNzbInfo()->GetParRenameStatus() <= NzbInfo::rsSkipped &&
m_hasParFiles)
{
PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name);
RequestParCheck(false);
}
else
#endif
if (m_hasRenamedArchiveFiles)
{
PrintMessage(Message::mkError, "Could not unpack %s due to renamed archive files", *m_name);
m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usFailure);
}
else
{
PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name);
m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped);
m_postInfo->SetStage(PostInfo::ptQueued);
}
}
else
{
PrintMessage(Message::mkInfo, "Unpack for %s skipped", *m_name);
m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped);
}
int unpackSec = (int)(Util::CurrentTime() - start);
m_postInfo->GetNzbInfo()->SetUnpackSec(m_postInfo->GetNzbInfo()->GetUnpackSec() + unpackSec);
@@ -268,8 +277,9 @@ void UnpackController::ExecuteUnrar(const char* password)
params.emplace_back("-o+");
}
params.emplace_back(m_hasNonStdRarFiles ? "*.*" : "*.rar");
params.push_back(FileSystem::MakeExtendedPath(BString<1024>("%s%c", *m_unpackDir, PATH_SEPARATOR), true));
params.emplace_back("*.rar");
m_unpackExtendedDir = FileSystem::MakeExtendedPath(m_unpackDir, true);
params.push_back(*BString<1024>("%s%c", *m_unpackExtendedDir, PATH_SEPARATOR));
SetArgs(std::move(params));
SetLogPrefix("Unrar");
ResetEnv();
@@ -532,9 +542,8 @@ void UnpackController::Completed()
if (g_Options->GetParRename())
{
//request par-rename check for extracted files
m_postInfo->GetNzbInfo()->SetRenameStatus(NzbInfo::rsNone);
m_postInfo->GetNzbInfo()->SetParRenameStatus(NzbInfo::rsNone);
}
m_postInfo->SetStage(PostInfo::ptQueued);
}
else
{
@@ -558,7 +567,6 @@ void UnpackController::Completed()
m_unpackSpaceError ? NzbInfo::usSpace :
m_unpackPasswordError || m_unpackDecryptError ? NzbInfo::usPassword :
NzbInfo::usFailure);
m_postInfo->SetStage(PostInfo::ptQueued);
}
}
}
@@ -569,7 +577,6 @@ void UnpackController::RequestParCheck(bool forceRepair)
PrintMessage(Message::mkInfo, "%s requested %s", *m_infoNameUp, forceRepair ? "par-check with forced repair" : "par-check/repair");
m_postInfo->SetRequestParCheck(true);
m_postInfo->SetForceRepair(forceRepair);
m_postInfo->SetStage(PostInfo::ptFinished);
m_postInfo->SetUnpackTried(true);
m_postInfo->SetPassListTried(m_passListTried);
m_postInfo->SetLastUnpackStatus((int)(m_unpackSpaceError ? NzbInfo::usSpace :
@@ -609,19 +616,12 @@ void UnpackController::CreateUnpackDir()
}
}
void UnpackController::CheckArchiveFiles(bool scanNonStdFiles)
void UnpackController::CheckArchiveFiles()
{
m_hasRarFiles = false;
m_hasNonStdRarFiles = false;
m_hasSevenZipFiles = false;
m_hasSevenZipMultiFiles = false;
m_hasSplittedFiles = false;
RegEx regExRar(".*\\.rar$");
RegEx regExRarMultiSeq(".*\\.(r|s)[0-9][0-9]$");
RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
RegEx regExSevenZip(".*\\.7z$");
RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$");
RegEx regExNumExt(".*\\.[0-9]+$");
RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$");
DirBrowser dir(m_destDir);
@@ -637,6 +637,10 @@ void UnpackController::CheckArchiveFiles(bool scanNonStdFiles)
if (regExRar.Match(filename))
{
m_hasRarFiles = true;
m_hasUnpackedRarFiles |= std::find(
m_postInfo->GetExtractedArchives()->begin(),
m_postInfo->GetExtractedArchives()->end(),
filename) == m_postInfo->GetExtractedArchives()->end();
}
else if (regExSevenZip.Match(filename))
{
@@ -646,16 +650,16 @@ void UnpackController::CheckArchiveFiles(bool scanNonStdFiles)
{
m_hasSevenZipMultiFiles = true;
}
else if (scanNonStdFiles && !m_hasNonStdRarFiles && extNum > 1 &&
!regExRarMultiSeq.Match(filename) && regExNumExt.Match(filename) &&
FileHasRarSignature(fullFilename))
{
m_hasNonStdRarFiles = true;
}
else if (regExSplitExt.Match(filename) && (extNum == 0 || extNum == 1))
{
m_hasSplittedFiles = true;
}
else if (!m_hasRenamedArchiveFiles && !regExRarMultiSeq.Match(filename) &&
!Util::MatchFileExt(filename, g_Options->GetUnpackIgnoreExt(), ",;") &&
FileHasRarSignature(fullFilename))
{
m_hasRenamedArchiveFiles = true;
}
}
}
}
@@ -727,7 +731,7 @@ bool UnpackController::Cleanup()
if (!m_unpackOk && m_finalDirCreated)
{
FileSystem::RemoveDirectory(m_finalDir);
FileSystem::DeleteDirectory(m_finalDir);
}
if (m_unpackOk && ok && g_Options->GetUnpackCleanupDisk())
@@ -737,7 +741,6 @@ bool UnpackController::Cleanup()
RegEx regExRar(".*\\.rar$");
RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]+$");
RegEx regExNumExt(".*\\.[0-9]+$");
RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$");
DirBrowser dir(m_destDir);
@@ -749,7 +752,6 @@ bool UnpackController::Cleanup()
(m_interDir || !extractedFiles.Exists(filename)) &&
(regExRar.Match(filename) || regExSevenZip.Match(filename) ||
(regExRarMultiSeq.Match(filename) && FileHasRarSignature(fullFilename)) ||
(m_hasNonStdRarFiles && regExNumExt.Match(filename) && FileHasRarSignature(fullFilename)) ||
(m_hasSplittedFiles && regExSplitExt.Match(filename) && m_joinedFiles.Exists(filename))))
{
PrintMessage(Message::mkInfo, "Deleting file %s", filename);
@@ -837,9 +839,9 @@ void UnpackController::AddMessage(Message::EKind kind, const char* text)
// Modify unrar messages for better readability:
// remove the destination path part from message "Extracting file.xxx"
if (m_unpacker == upUnrar && !strncmp(text, "Unrar: Extracting ", 19) &&
!strncmp(text + 19, m_unpackDir, strlen(m_unpackDir)))
!strncmp(text + 19, m_unpackExtendedDir, strlen(m_unpackExtendedDir)))
{
msgText.Format("Unrar: Extracting %s", text + 19 + strlen(m_unpackDir) + 1);
msgText.Format("Unrar: Extracting %s", text + 19 + strlen(m_unpackExtendedDir) + 1);
}
m_postInfo->GetNzbInfo()->AddMessage(kind, msgText);
@@ -871,7 +873,7 @@ void UnpackController::AddMessage(Message::EKind kind, const char* text)
m_unpackDecryptError = true;
}
if (m_unpacker == upUnrar && !strncmp(text, "Unrar: The specified password is incorrect.'", 43))
if (m_unpacker == upUnrar && !strncmp(text, "Unrar: The specified password is incorrect.", 43))
{
m_unpackPasswordError = true;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -65,27 +65,29 @@ private:
CString m_destDir;
CString m_finalDir;
CString m_unpackDir;
CString m_unpackExtendedDir;
CString m_password;
bool m_interDir;
bool m_allOkMessageReceived;
bool m_noFilesMessageReceived;
bool m_hasParFiles;
bool m_hasRarFiles;
bool m_hasNonStdRarFiles;
bool m_hasSevenZipFiles;
bool m_hasSevenZipMultiFiles;
bool m_hasSplittedFiles;
bool m_unpackOk;
bool m_unpackStartError;
bool m_unpackSpaceError;
bool m_unpackDecryptError;
bool m_unpackPasswordError;
bool m_cleanedUpDisk;
bool m_autoTerminated;
EUnpacker m_unpacker;
bool m_finalDirCreated;
bool m_interDir = false;
bool m_allOkMessageReceived = false;
bool m_noFilesMessageReceived = false;
bool m_hasParFiles = false;
bool m_hasRarFiles = false;
bool m_hasUnpackedRarFiles = false;
bool m_hasRenamedArchiveFiles = false;
bool m_hasSevenZipFiles = false;
bool m_hasSevenZipMultiFiles = false;
bool m_hasSplittedFiles = false;
bool m_unpackOk = false;
bool m_unpackStartError = false;
bool m_unpackSpaceError = false;
bool m_unpackDecryptError = false;
bool m_unpackPasswordError = false;
bool m_cleanedUpDisk = false;
bool m_autoTerminated = false;
bool m_finalDirCreated = false;
bool m_passListTried = false;
FileList m_joinedFiles;
bool m_passListTried;
void ExecuteUnpack(EUnpacker unpacker, const char* password, bool multiVolumes);
void ExecuteUnrar(const char* password);
@@ -96,7 +98,7 @@ private:
void Completed();
void CreateUnpackDir();
bool Cleanup();
void CheckArchiveFiles(bool scanNonStdFiles);
void CheckArchiveFiles();
void SetProgressLabel(const char* progressLabel);
#ifndef DISABLE_PARCHECK
void RequestParCheck(bool forceRepair);

View File

@@ -0,0 +1,549 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "DirectRenamer.h"
#include "Options.h"
#include "FileSystem.h"
#include "ParParser.h"
#ifndef DISABLE_PARCHECK
#include "par2cmdline.h"
#include "par2fileformat.h"
#include "md5.h"
#endif
class RenameContentAnalyzer : public ArticleContentAnalyzer
{
public:
virtual void Reset();
virtual void Append(const void* buffer, int len);
void Finish();
const char* GetHash16k() { return m_hash16k; }
bool GetParFile() { return m_parFile; }
const char* GetParSetId() { return m_parSetId; }
private:
#ifndef DISABLE_PARCHECK
Par2::MD5Context m_md5Context;
char m_signature[sizeof(Par2::PACKET_HEADER)];
#endif
int m_dataSize = 0;
CString m_hash16k;
CString m_parSetId;
bool m_parFile = false;
};
#ifndef DISABLE_PARCHECK
class DirectParRepairer : public Par2::Par2Repairer
{
public:
DirectParRepairer() : Par2::Par2Repairer(m_nout, m_nout) {};
friend class DirectParLoader;
private:
class NullStreamBuf : public std::streambuf {};
NullStreamBuf m_nullbuf;
std::ostream m_nout{&m_nullbuf};
};
class DirectParLoader : public Thread
{
public:
static void StartLoader(DirectRenamer* owner, NzbInfo* nzbInfo);
virtual void Run();
private:
typedef std::vector<CString> ParFiles;
DirectRenamer* m_owner;
ParFiles m_parFiles;
DirectRenamer::FileHashList m_parHashes;
int m_nzbId;
void LoadParFile(const char* parFile);
};
void DirectParLoader::StartLoader(DirectRenamer* owner, NzbInfo* nzbInfo)
{
nzbInfo->PrintMessage(Message::mkInfo, "Directly checking renamed files for %s", nzbInfo->GetName());
DirectParLoader* directParLoader = new DirectParLoader();
directParLoader->m_owner = owner;
directParLoader->m_nzbId = nzbInfo->GetId();
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
if (completedFile.GetParFile())
{
directParLoader->m_parFiles.emplace_back(BString<1024>("%s%c%s",
nzbInfo->GetDestDir(), PATH_SEPARATOR, completedFile.GetFilename()));
}
}
directParLoader->SetAutoDestroy(true);
directParLoader->Start();
}
void DirectParLoader::Run()
{
debug("Started DirectParLoader");
for (CString& parFile : m_parFiles)
{
LoadParFile(parFile);
}
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (nzbInfo)
{
// nzb is still in queue
m_owner->RenameFiles(downloadQueue, nzbInfo, &m_parHashes);
}
}
void DirectParLoader::LoadParFile(const char* parFile)
{
DirectParRepairer repairer;
if (!repairer.LoadPacketsFromFile(parFile))
{
warn("Could not load par2-file %s", parFile);
return;
}
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (!nzbInfo)
{
// nzb isn't in queue anymore
return;
}
nzbInfo->PrintMessage(Message::mkInfo, "Loaded par2-file %s for direct-rename", FileSystem::BaseFileName(parFile));
for (std::pair<const Par2::MD5Hash, Par2::Par2RepairerSourceFile*>& entry : repairer.sourcefilemap)
{
if (IsStopped())
{
break;
}
Par2::Par2RepairerSourceFile* sourceFile = entry.second;
if (!sourceFile || !sourceFile->GetDescriptionPacket())
{
nzbInfo->PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", FileSystem::BaseFileName(parFile));
return;
}
std::string filename = Par2::DiskFile::TranslateFilename(sourceFile->GetDescriptionPacket()->FileName());
std::string hash = sourceFile->GetDescriptionPacket()->Hash16k().print();
debug("file: %s, hash-16k: %s", filename.c_str(), hash.c_str());
m_parHashes.emplace_back(filename.c_str(), hash.c_str());
}
}
#endif
std::unique_ptr<ArticleContentAnalyzer> DirectRenamer::MakeArticleContentAnalyzer()
{
return std::make_unique<RenameContentAnalyzer>();
}
void DirectRenamer::ArticleDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo,
ArticleInfo* articleInfo, ArticleContentAnalyzer* articleContentAnalyzer)
{
debug("Applying analyzer data %s for ", fileInfo->GetFilename());
RenameContentAnalyzer* contentAnalyzer = (RenameContentAnalyzer*)articleContentAnalyzer;
contentAnalyzer->Finish();
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
// we don't support analyzing of files split into articles smaller than 16KB
if (articleInfo->GetSize() >= 16 * 1024 || fileInfo->GetArticles()->size() == 1)
{
fileInfo->SetHash16k(contentAnalyzer->GetHash16k());
debug("file: %s; article-hash16k: %s", fileInfo->GetFilename(), fileInfo->GetHash16k());
}
detail("Detected %s %s", (contentAnalyzer->GetParFile() ? "par2-file" : "non-par2-file"), fileInfo->GetFilename());
if (fileInfo->GetParFile() != contentAnalyzer->GetParFile())
{
debug("Changing par2-flag for %s", fileInfo->GetFilename());
fileInfo->SetParFile(contentAnalyzer->GetParFile());
int delta = fileInfo->GetParFile() ? 1 : -1;
nzbInfo->SetParSize(nzbInfo->GetParSize() + fileInfo->GetSize() * delta);
nzbInfo->SetParCurrentSuccessSize(nzbInfo->GetParCurrentSuccessSize() + fileInfo->GetSuccessSize() * delta);
nzbInfo->SetParCurrentFailedSize(nzbInfo->GetParCurrentFailedSize() +
fileInfo->GetFailedSize() * delta + fileInfo->GetMissedSize() * delta);
nzbInfo->SetRemainingParCount(nzbInfo->GetRemainingParCount() + 1 * delta);
downloadQueue->Save();
}
if (fileInfo->GetParFile())
{
fileInfo->SetParSetId(contentAnalyzer->GetParSetId());
debug("file: %s; setid: %s", fileInfo->GetFilename(), fileInfo->GetParSetId());
}
CheckState(downloadQueue, nzbInfo);
}
void DirectRenamer::FileDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo)
{
CheckState(downloadQueue, fileInfo->GetNzbInfo());
}
void DirectRenamer::CheckState(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
#ifndef DISABLE_PARCHECK
if (nzbInfo->GetDirectRenameStatus() > NzbInfo::tsRunning)
{
return;
}
// check if all first articles are successfully downloaded (1)
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if (Util::EmptyStr(fileInfo->GetHash16k()) ||
(fileInfo->GetParFile() && Util::EmptyStr(fileInfo->GetParSetId())))
{
return;
}
}
// check if all first articles are successfully downloaded (2)
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
if (Util::EmptyStr(completedFile.GetHash16k()) ||
(completedFile.GetParFile() && Util::EmptyStr(completedFile.GetParSetId())))
{
return;
}
}
if (!nzbInfo->GetWaitingPar())
{
// all first articles downloaded
UnpausePars(nzbInfo);
nzbInfo->SetWaitingPar(true);
downloadQueue->Save();
}
if (nzbInfo->GetWaitingPar() && !nzbInfo->GetLoadingPar())
{
// check if all par2-files scheduled for downloading already completed
FileList::iterator pos = std::find_if(
nzbInfo->GetFileList()->begin(), nzbInfo->GetFileList()->end(),
[](std::unique_ptr<FileInfo>& fileInfo)
{
return fileInfo->GetExtraPriority();
});
if (pos == nzbInfo->GetFileList()->end())
{
// all wanted par2-files are downloaded
nzbInfo->SetLoadingPar(true);
DirectParLoader::StartLoader(this, nzbInfo);
return;
}
}
#endif
}
// Unpause smallest par-files from each par-set
void DirectRenamer::UnpausePars(NzbInfo* nzbInfo)
{
ParFileList parFiles;
CollectPars(nzbInfo, &parFiles);
std::vector<CString> parsets;
// sort by size
std::sort(parFiles.begin(), parFiles.end(),
[nzbInfo](const ParFile& parFile1, const ParFile& parFile2)
{
FileInfo* fileInfo1 = nzbInfo->GetFileList()->Find(const_cast<ParFile&>(parFile1).GetId());
FileInfo* fileInfo2 = nzbInfo->GetFileList()->Find(const_cast<ParFile&>(parFile2).GetId());
return (!fileInfo1 && fileInfo2) ||
(fileInfo1 && fileInfo2 && fileInfo1->GetSize() < fileInfo2->GetSize());
});
// 1. count already downloaded files
for (ParFile& parFile : parFiles)
{
if (parFile.GetCompleted())
{
parsets.emplace_back(parFile.GetSetId());
}
}
// 2. find smallest par-file from each par-set from not yet completely downloaded files
for (ParFile& parFile : parFiles)
{
std::vector<CString>::iterator pos = std::find(parsets.begin(), parsets.end(), parFile.GetSetId());
if (pos == parsets.end())
{
// this par-set is not yet downloaded
parsets.emplace_back(parFile.GetSetId());
FileInfo* fileInfo = nzbInfo->GetFileList()->Find(parFile.GetId());
if (fileInfo)
{
nzbInfo->PrintMessage(Message::mkDetail, "Increasing priority for par2-file %s", fileInfo->GetFilename());
fileInfo->SetPaused(false);
fileInfo->SetExtraPriority(true);
}
}
}
}
void DirectRenamer::CollectPars(NzbInfo* nzbInfo, ParFileList* parFiles)
{
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if (fileInfo->GetParFile())
{
parFiles->emplace_back(fileInfo->GetId(), fileInfo->GetFilename(), fileInfo->GetParSetId(), false);
}
}
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
if (completedFile.GetParFile())
{
parFiles->emplace_back(completedFile.GetId(), completedFile.GetFilename(), completedFile.GetParSetId(), true);
}
}
}
void DirectRenamer::RenameFiles(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, FileHashList* parHashes)
{
int renamedCount = 0;
bool renamePars = NeedRenamePars(nzbInfo);
int vol = 1;
// rename in-progress files
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
CString newName;
if (fileInfo->GetParFile() && renamePars)
{
newName = BuildNewParName(fileInfo->GetFilename(), nzbInfo->GetDestDir(), fileInfo->GetParSetId(), vol);
}
else if (!fileInfo->GetParFile())
{
newName = BuildNewRegularName(fileInfo->GetFilename(), parHashes, fileInfo->GetHash16k());
}
if (newName)
{
bool written = fileInfo->GetOutputFilename() &&
!Util::EndsWith(fileInfo->GetOutputFilename(), ".out.tmp", true);
if (!written)
{
nzbInfo->PrintMessage(Message::mkInfo, "Renaming in-progress file %s to %s",
fileInfo->GetFilename(), *newName);
fileInfo->SetFilename(newName);
fileInfo->SetFilenameConfirmed(true);
renamedCount++;
}
else if (RenameCompletedFile(nzbInfo, fileInfo->GetFilename(), newName))
{
fileInfo->SetFilename(newName);
fileInfo->SetFilenameConfirmed(true);
renamedCount++;
}
}
}
// rename completed files
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
CString newName;
if (completedFile.GetParFile() && renamePars)
{
newName = BuildNewParName(completedFile.GetFilename(), nzbInfo->GetDestDir(), completedFile.GetParSetId(), vol);
}
else if (!completedFile.GetParFile())
{
newName = BuildNewRegularName(completedFile.GetFilename(), parHashes, completedFile.GetHash16k());
}
if (newName && RenameCompletedFile(nzbInfo, completedFile.GetFilename(), newName))
{
completedFile.SetFilename(newName);
renamedCount++;
}
}
if (renamedCount > 0)
{
nzbInfo->PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", renamedCount, nzbInfo->GetName());
}
else
{
nzbInfo->PrintMessage(Message::mkInfo, "No renamed files found for %s", nzbInfo->GetName());
}
RenameCompleted(downloadQueue, nzbInfo);
}
CString DirectRenamer::BuildNewRegularName(const char* oldName, FileHashList* parHashes, const char* hash16k)
{
if (Util::EmptyStr(hash16k))
{
return nullptr;
}
FileHashList::iterator pos = std::find_if(parHashes->begin(), parHashes->end(),
[hash16k](FileHash& parHash)
{
return !strcmp(parHash.GetHash(), hash16k);
});
if (pos != parHashes->end())
{
FileHash& parHash = *pos;
if (strcasecmp(oldName, parHash.GetFilename()))
{
return parHash.GetFilename();
}
}
return nullptr;
}
CString DirectRenamer::BuildNewParName(const char* oldName, const char* destDir, const char* setId, int& vol)
{
BString<1024> newName;
BString<1024> destFileName;
// trying to reuse file suffix
const char* suffix = strstr(oldName, ".vol");
const char* extension = suffix ? strrchr(suffix, '.') : nullptr;
if (suffix && extension && !strcasecmp(extension, ".par2"))
{
newName.Format("%s%s", setId, suffix);
destFileName.Format("%s%c%s", destDir, PATH_SEPARATOR, *newName);
}
while (destFileName.Empty() || FileSystem::FileExists(destFileName))
{
newName.Format("%s.vol%03i+01.PAR2", setId, vol);
destFileName.Format("%s%c%s", destDir, PATH_SEPARATOR, *newName);
vol++;
}
return *newName;
}
bool DirectRenamer::NeedRenamePars(NzbInfo* nzbInfo)
{
// renaming is needed if par2-files from same par-set have different base names
// or if any par2-file has non .par2-extension
ParFileList parFiles;
CollectPars(nzbInfo, &parFiles);
for (ParFile& parFile : parFiles)
{
if (!Util::EndsWith(parFile.GetFilename(), ".par2", false))
{
return true;
}
for (ParFile& parFile2 : parFiles)
{
if (&parFile != &parFile2 && !strcmp(parFile.GetSetId(), parFile2.GetSetId()) &&
!ParParser::SameParCollection(parFile.GetFilename(), parFile2.GetFilename(), false))
{
return true;
}
}
}
return false;
}
bool DirectRenamer::RenameCompletedFile(NzbInfo* nzbInfo, const char* oldName, const char* newName)
{
BString<1024> oldFullFilename("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, oldName);
BString<1024> newFullFilename("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, newName);
nzbInfo->PrintMessage(Message::mkInfo, "Renaming completed file %s to %s", oldName, newName);
if (!FileSystem::MoveFile(oldFullFilename, newFullFilename))
{
nzbInfo->PrintMessage(Message::mkError, "Could not rename completed file %s to %s: %s",
*oldFullFilename, *newFullFilename, *FileSystem::GetLastErrorMessage());
return false;
}
return true;
}
void RenameContentAnalyzer::Reset()
{
#ifndef DISABLE_PARCHECK
m_md5Context.Reset();
#endif
m_dataSize = 0;
}
void RenameContentAnalyzer::Append(const void* buffer, int len)
{
#ifndef DISABLE_PARCHECK
if (m_dataSize < sizeof(m_signature))
{
memcpy(m_signature + m_dataSize, buffer, std::min((size_t)len, sizeof(m_signature) - m_dataSize));
}
if (m_dataSize >= sizeof(m_signature) && (*(Par2::MAGIC*)m_signature) == Par2::packet_magic)
{
m_parFile = true;
m_parSetId = ((Par2::PACKET_HEADER*)m_signature)->setid.print().c_str();
}
int rem16kSize = std::min(len, 16 * 1024 - m_dataSize);
if (rem16kSize > 0)
{
m_md5Context.Update(buffer, rem16kSize);
}
m_dataSize += len;
#endif
}
// Must be called with locked DownloadQueue
void RenameContentAnalyzer::Finish()
{
#ifndef DISABLE_PARCHECK
Par2::MD5Hash hash;
m_md5Context.Final(hash);
m_hash16k = hash.print().c_str();
#endif
}

View File

@@ -0,0 +1,84 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DIRECTRENAMER_H
#define DIRECTRENAMER_H
#include "ArticleDownloader.h"
class DirectRenamer
{
public:
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; }
private:
CString m_filename;
CString m_hash;
};
typedef std::deque<FileHash> FileHashList;
class ParFile
{
public:
ParFile(int id, const char* filename, const char* setId, bool completed) :
m_id(id), m_filename(filename), m_setId(setId), m_completed(completed) {}
int GetId() { return m_id; }
const char* GetFilename() { return m_filename; }
const char* GetSetId() { return m_setId; }
bool GetCompleted() { return m_completed; }
private:
int m_id;
CString m_filename;
CString m_setId;
bool m_completed = false;
};
typedef std::deque<ParFile> ParFileList;
std::unique_ptr<ArticleContentAnalyzer> MakeArticleContentAnalyzer();
void ArticleDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo,
ArticleInfo* articleInfo, ArticleContentAnalyzer* articleContentAnalyzer);
void FileDownloaded(DownloadQueue* downloadQueue, FileInfo* fileInfo);
protected:
virtual void RenameCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo) = 0;
private:
void CheckState(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void UnpausePars(NzbInfo* nzbInfo);
void RenameFiles(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, FileHashList* parHashes);
bool RenameCompletedFile(NzbInfo* nzbInfo, const char* oldName, const char* newName);
bool NeedRenamePars(NzbInfo* nzbInfo);
void CollectPars(NzbInfo* nzbInfo, ParFileList* parFiles);
CString BuildNewRegularName(const char* oldName, FileHashList* parHashes, const char* hash16k);
CString BuildNewParName(const char* oldName, const char* destDir, const char* setId, int& vol);
friend class DirectParLoader;
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -265,7 +265,7 @@ bool DiskState::SaveDownloadQueue(DownloadQueue* downloadQueue, bool saveHistory
bool ok = true;
{
StateFile stateFile("queue", 57, true);
StateFile stateFile("queue", 60, true);
if (!downloadQueue->GetQueue()->empty())
{
StateDiskFile* outfile = stateFile.BeginWrite();
@@ -288,7 +288,7 @@ bool DiskState::SaveDownloadQueue(DownloadQueue* downloadQueue, bool saveHistory
if (saveHistory)
{
StateFile stateFile("history", 57, true);
StateFile stateFile("history", 60, true);
if (!downloadQueue->GetHistory()->empty())
{
StateDiskFile* outfile = stateFile.BeginWrite();
@@ -320,7 +320,7 @@ bool DiskState::LoadDownloadQueue(DownloadQueue* downloadQueue, Servers* servers
int formatVersion = 0;
{
StateFile stateFile("queue", 57, true);
StateFile stateFile("queue", 60, true);
if (stateFile.FileExists())
{
StateDiskFile* infile = stateFile.BeginRead();
@@ -349,7 +349,7 @@ bool DiskState::LoadDownloadQueue(DownloadQueue* downloadQueue, Servers* servers
if (formatVersion == 0 || formatVersion >= 57)
{
StateFile stateFile("history", 57, true);
StateFile stateFile("history", 60, true);
if (stateFile.FileExists())
{
StateDiskFile* infile = stateFile.BeginRead();
@@ -428,9 +428,10 @@ void DiskState::SaveNzbInfo(NzbInfo* nzbInfo, StateDiskFile& outfile)
outfile.PrintLine("%i,%i,%i,%i,%i", (int)nzbInfo->GetPriority(),
nzbInfo->GetPostInfo() ? (int)nzbInfo->GetPostInfo()->GetStage() + 1 : 0,
(int)nzbInfo->GetDeletePaused(), (int)nzbInfo->GetManyDupeFiles(), nzbInfo->GetFeedId());
outfile.PrintLine("%i,%i,%i,%i,%i,%i,%i", (int)nzbInfo->GetParStatus(), (int)nzbInfo->GetUnpackStatus(),
(int)nzbInfo->GetMoveStatus(), (int)nzbInfo->GetRenameStatus(), (int)nzbInfo->GetDeleteStatus(),
(int)nzbInfo->GetMarkStatus(), (int)nzbInfo->GetUrlStatus());
outfile.PrintLine("%i,%i,%i,%i,%i,%i,%i,%i,%i", (int)nzbInfo->GetParStatus(), (int)nzbInfo->GetUnpackStatus(),
(int)nzbInfo->GetMoveStatus(), (int)nzbInfo->GetParRenameStatus(), (int)nzbInfo->GetRarRenameStatus(),
(int)nzbInfo->GetDirectRenameStatus(), (int)nzbInfo->GetDeleteStatus(), (int)nzbInfo->GetMarkStatus(),
(int)nzbInfo->GetUrlStatus());
outfile.PrintLine("%i,%i,%i", (int)nzbInfo->GetUnpackCleanedUpDisk(), (int)nzbInfo->GetHealthPaused(),
(int)nzbInfo->GetAddUrlPaused());
outfile.PrintLine("%i,%i,%i", nzbInfo->GetFileCount(), nzbInfo->GetParkedFileCount(),
@@ -466,8 +467,11 @@ void DiskState::SaveNzbInfo(NzbInfo* nzbInfo, StateDiskFile& outfile)
outfile.PrintLine("%i", (int)nzbInfo->GetCompletedFiles()->size());
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
{
outfile.PrintLine("%i,%i,%u,%s", completedFile.GetId(), (int)completedFile.GetStatus(),
completedFile.GetCrc(), completedFile.GetFileName());
outfile.PrintLine("%i,%i,%u,%i,%s,%s,%s", completedFile.GetId(), (int)completedFile.GetStatus(),
completedFile.GetCrc(), (int)completedFile.GetParFile(),
completedFile.GetHash16k() ? completedFile.GetHash16k() : "",
completedFile.GetParSetId() ? completedFile.GetParSetId() : "",
completedFile.GetFilename());
}
outfile.PrintLine("%i", (int)nzbInfo->GetParameters()->size());
@@ -556,17 +560,45 @@ bool DiskState::LoadNzbInfo(NzbInfo* nzbInfo, Servers* servers, StateDiskFile& i
if (postStage > 0)
{
nzbInfo->EnterPostProcess();
if (formatVersion < 59 && postStage == 6)
{
postStage++;
}
else if (formatVersion < 59 && postStage > 6)
{
postStage += 2;
}
nzbInfo->GetPostInfo()->SetStage((PostInfo::EStage)postStage);
}
nzbInfo->SetFeedId(feedId);
int parStatus, unpackStatus, moveStatus, renameStatus, deleteStatus, markStatus, urlStatus;
if (infile.ScanLine("%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
&renameStatus, &deleteStatus, &markStatus, &urlStatus) != 7) goto error;
int parStatus, unpackStatus, moveStatus, parRenameStatus, rarRenameStatus,
directRenameStatus, deleteStatus, markStatus, urlStatus;
if (formatVersion >= 60)
{
if (infile.ScanLine("%i,%i,%i,%i,%i,%i,%i,%i,%i", &parStatus,
&unpackStatus, &moveStatus, &parRenameStatus, &rarRenameStatus, &directRenameStatus,
&deleteStatus, &markStatus, &urlStatus) != 9) goto error;
}
else if (formatVersion >= 58)
{
directRenameStatus = 0;
if (infile.ScanLine("%i,%i,%i,%i,%i,%i,%i,%i", &parStatus,
&unpackStatus, &moveStatus, &parRenameStatus, &rarRenameStatus, &deleteStatus,
&markStatus, &urlStatus) != 8) goto error;
}
else
{
rarRenameStatus = directRenameStatus = 0;
if (infile.ScanLine("%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
&parRenameStatus, &deleteStatus, &markStatus, &urlStatus) != 7) goto error;
}
nzbInfo->SetParStatus((NzbInfo::EParStatus)parStatus);
nzbInfo->SetUnpackStatus((NzbInfo::EUnpackStatus)unpackStatus);
nzbInfo->SetUnpackStatus((NzbInfo::EPostUnpackStatus)unpackStatus);
nzbInfo->SetMoveStatus((NzbInfo::EMoveStatus)moveStatus);
nzbInfo->SetRenameStatus((NzbInfo::ERenameStatus)renameStatus);
nzbInfo->SetParRenameStatus((NzbInfo::EPostRenameStatus)parRenameStatus);
nzbInfo->SetRarRenameStatus((NzbInfo::EPostRenameStatus)rarRenameStatus);
nzbInfo->SetDirectRenameStatus((NzbInfo::EDirectRenameStatus)directRenameStatus);
nzbInfo->SetDeleteStatus((NzbInfo::EDeleteStatus)deleteStatus);
nzbInfo->SetMarkStatus((NzbInfo::EMarkStatus)markStatus);
if (nzbInfo->GetKind() == NzbInfo::nkNzb ||
@@ -678,10 +710,31 @@ bool DiskState::LoadNzbInfo(NzbInfo* nzbInfo, Servers* servers, StateDiskFile& i
char* fileName = buf;
int status = 0;
uint32 crc = 0;
int parFile = 0;
char* hash16k = nullptr;
char* parSetId = nullptr;
if (formatVersion >= 49)
{
if (formatVersion >= 50)
if (formatVersion >= 60)
{
if (sscanf(buf, "%i,%i,%u,%i", &id, &status, &crc, &parFile) != 4) goto error;
hash16k = strchr(buf, ',');
if (hash16k) hash16k = strchr(hash16k+1, ',');
if (hash16k) hash16k = strchr(hash16k+1, ',');
if (hash16k) hash16k = strchr(hash16k+1, ',');
if (hash16k)
{
parSetId = strchr(++hash16k, ',');
if (parSetId)
{
*parSetId++ = '\0';
fileName = strchr(parSetId, ',');
if (fileName) *fileName = '\0';
}
}
}
else if (formatVersion >= 50)
{
if (sscanf(buf, "%i,%i,%u", &id, &status, &crc) != 3) goto error;
fileName = strchr(buf, ',');
@@ -699,7 +752,10 @@ bool DiskState::LoadNzbInfo(NzbInfo* nzbInfo, Servers* servers, StateDiskFile& i
}
}
nzbInfo->GetCompletedFiles()->emplace_back(id, fileName, (CompletedFile::EStatus)status, crc);
nzbInfo->GetCompletedFiles()->emplace_back(id, fileName,
(CompletedFile::EStatus)status, crc, (bool)parFile,
Util::EmptyStr(hash16k) ? nullptr : hash16k,
Util::EmptyStr(parSetId) ? nullptr : parSetId);
}
int parameterCount;
@@ -772,7 +828,7 @@ bool DiskState::LoadNzbInfo(NzbInfo* nzbInfo, Servers* servers, StateDiskFile& i
{
fileInfo->SetTime(time);
}
fileInfo->SetExtraPriority(extraPriority != 0);
fileInfo->SetExtraPriority((bool)extraPriority);
fileInfo->SetNzbInfo(nzbInfo);
nzbInfo->GetFileList()->Add(std::move(fileInfo));
}
@@ -828,7 +884,7 @@ bool DiskState::SaveFile(FileInfo* fileInfo)
debug("Saving FileInfo %i to disk", fileInfo->GetId());
BString<100> filename("%i", fileInfo->GetId());
StateFile stateFile(filename, 4, false);
StateFile stateFile(filename, 5, false);
StateDiskFile* outfile = stateFile.BeginWrite();
if (!outfile)
@@ -844,7 +900,7 @@ bool DiskState::SaveFileInfo(FileInfo* fileInfo, StateDiskFile& outfile)
outfile.PrintLine("%s", fileInfo->GetSubject());
outfile.PrintLine("%s", fileInfo->GetFilename());
outfile.PrintLine("%i", (int)fileInfo->GetTime());
outfile.PrintLine("%i,%i", (int)fileInfo->GetFilenameConfirmed(), (int)fileInfo->GetTime());
uint32 High, Low;
Util::SplitInt64(fileInfo->GetSize(), &High, &Low);
@@ -882,7 +938,7 @@ bool DiskState::LoadFile(FileInfo* fileInfo, bool fileSummary, bool articles)
debug("Loading FileInfo %i from disk", fileInfo->GetId());
BString<100> filename("%i", fileInfo->GetId());
StateFile stateFile(filename, 4, false);
StateFile stateFile(filename, 5, false);
StateDiskFile* infile = stateFile.BeginRead();
if (!infile)
@@ -903,7 +959,14 @@ bool DiskState::LoadFileInfo(FileInfo* fileInfo, StateDiskFile& infile, int form
if (!infile.ReadLine(buf, sizeof(buf))) goto error;
if (fileSummary) fileInfo->SetFilename(buf);
if (formatVersion >= 4)
if (formatVersion >= 5)
{
int time, filenameConfirmed;
if (infile.ScanLine("%i,%i", &filenameConfirmed, &time) != 2) goto error;
if (fileSummary) fileInfo->SetFilenameConfirmed((bool)filenameConfirmed);
if (fileSummary) fileInfo->SetTime((time_t)time);
}
else if (formatVersion >= 4)
{
int time;
if (infile.ScanLine("%i", &time) != 1) goto error;
@@ -967,7 +1030,7 @@ bool DiskState::SaveFileState(FileInfo* fileInfo, bool completed)
debug("Saving FileState %i to disk", fileInfo->GetId());
BString<100> filename("%i%s", fileInfo->GetId(), completed ? "c" : "s");
StateFile stateFile(filename, 4, false);
StateFile stateFile(filename, 5, false);
StateDiskFile* outfile = stateFile.BeginWrite();
if (!outfile)
@@ -989,6 +1052,8 @@ bool DiskState::SaveFileState(FileInfo* fileInfo, StateDiskFile& outfile, bool c
outfile.PrintLine("%u,%u,%u,%u,%u,%u", High1, Low1, High2, Low2, High3, Low3);
outfile.PrintLine("%s", fileInfo->GetFilename());
outfile.PrintLine("%s", fileInfo->GetHash16k());
outfile.PrintLine("%i", (int)fileInfo->GetParFile());
SaveServerStats(fileInfo->GetServerStats(), outfile);
@@ -1008,7 +1073,7 @@ bool DiskState::LoadFileState(FileInfo* fileInfo, Servers* servers, bool complet
debug("Loading FileInfo %i from disk", fileInfo->GetId());
BString<100> filename("%i%s", fileInfo->GetId(), completed ? "c" : "s");
StateFile stateFile(filename, 4, false);
StateFile stateFile(filename, 5, false);
StateDiskFile* infile = stateFile.BeginRead();
if (!infile)
@@ -1034,13 +1099,23 @@ bool DiskState::LoadFileState(FileInfo* fileInfo, Servers* servers, StateDiskFil
fileInfo->SetSuccessSize(Util::JoinInt64(High2, Low2));
fileInfo->SetFailedSize(Util::JoinInt64(High3, Low3));
char buf[1024];
if (formatVersion >= 4)
{
char buf[1024];
if (!infile.ReadLine(buf, sizeof(buf))) goto error;
fileInfo->SetFilename(buf);
}
if (formatVersion >= 5)
{
if (!infile.ReadLine(buf, sizeof(buf))) goto error;
fileInfo->SetHash16k(*buf ? buf : nullptr);
int parFile = 0;
if (infile.ScanLine("%i", &parFile) != 1) goto error;
fileInfo->SetParFile((bool)parFile);
}
if (!LoadServerStats(fileInfo->GetServerStats(), servers, infile)) goto error;
int completedArticles;
@@ -1079,6 +1154,13 @@ bool DiskState::LoadFileState(FileInfo* fileInfo, Servers* servers, StateDiskFil
status = ArticleInfo::aiUndefined;
}
if (status == ArticleInfo::aiFinished && !g_Options->GetDirectWrite() &&
!fileInfo->GetForceDirectWrite() && !pa->GetResultFilename())
{
pa->SetResultFilename(BString<1024>("%s%c%i.%03i", g_Options->GetTempDir(),
PATH_SEPARATOR, fileInfo->GetId(), pa->GetPartNumber()));
}
// don't allow all articles be completed or the file will stuck.
// such states should never be saved on disk but just in case.
if (completedArticles == size - 1 && !completed)
@@ -1318,9 +1400,23 @@ void DiskState::CleanupTempDir(DownloadQueue* downloadQueue)
DirBrowser dir(g_Options->GetTempDir());
while (const char* filename = dir.Next())
{
bool garbage = strstr(filename, ".tmp") || strstr(filename, ".dec");
int id, part;
if (strstr(filename, ".tmp") || strstr(filename, ".dec") ||
(sscanf(filename, "%i.%i", &id, &part) == 2))
if (!garbage && sscanf(filename, "%i.%i", &id, &part) == 2)
{
garbage = true;
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
if (nzbInfo->GetFileList()->Find(id))
{
garbage = false;
break;
}
}
}
if (garbage)
{
BString<1024> fullFilename("%s%c%s", g_Options->GetTempDir(), PATH_SEPARATOR, filename);
FileSystem::DeleteFile(fullFilename);

View File

@@ -2,7 +2,7 @@
* 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>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -470,13 +470,20 @@ void NzbInfo::SetActiveDownloads(int activeDownloads)
if (activeDownloads > 0)
{
m_downloadStartTime = Util::CurrentTime();
m_downloadStartSec = m_downloadSec;
}
else
{
m_downloadSec += Util::CurrentTime() - m_downloadStartTime;
m_downloadSec = m_downloadStartSec + (Util::CurrentTime() - m_downloadStartTime);
m_downloadStartTime = 0;
m_changed = true;
}
}
else if (activeDownloads > 0)
{
m_downloadSec = m_downloadStartSec + (Util::CurrentTime() - m_downloadStartTime);
m_changed = true;
}
m_activeDownloads = activeDownloads;
}
@@ -725,6 +732,24 @@ void NzbInfo::UpdateDeletedStats(FileInfo* fileInfo)
m_currentServerStats.ListOp(fileInfo->GetServerStats(), ServerStatList::soSubtract);
}
bool NzbInfo::IsDownloadCompleted(bool ignorePausedPars)
{
if (m_activeDownloads)
{
return false;
}
for (FileInfo* fileInfo : &m_fileList)
{
if ((!fileInfo->GetPaused() || !ignorePausedPars || !fileInfo->GetParFile()) &&
!fileInfo->GetDeleted())
{
return false;
}
}
return true;
}
void ArticleInfo::AttachSegment(std::unique_ptr<SegmentData> content, int64 offset, int size)
{
@@ -791,9 +816,10 @@ void FileInfo::SetActiveDownloads(int activeDownloads)
}
CompletedFile::CompletedFile(int id, const char* fileName, EStatus status, uint32 crc) :
m_id(id), m_fileName(fileName), m_status(status), m_crc(crc)
CompletedFile::CompletedFile(int id, const char* filename, EStatus status, uint32 crc,
bool parFile, const char* hash16k, const char* parSetId) :
m_id(id), m_filename(filename), m_status(status), m_crc(crc), m_parFile(parFile),
m_hash16k(hash16k), m_parSetId(parSetId)
{
if (FileInfo::m_idMax < m_id)
{

View File

@@ -2,7 +2,7 @@
* 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>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -192,6 +192,11 @@ public:
void SetPartialState(EPartialState partialState) { m_partialState = partialState; }
uint32 GetCrc() { return m_crc; }
void SetCrc(uint32 crc) { m_crc = crc; }
const char* GetHash16k() { return m_hash16k; }
void SetHash16k(const char* hash16k) { m_hash16k = hash16k; }
const char* GetParSetId() { return m_parSetId; }
void SetParSetId(const char* parSetId) { m_parSetId = parSetId; }
ServerStatList* GetServerStats() { return &m_serverStats; }
private:
@@ -228,6 +233,8 @@ private:
bool m_forceDirectWrite = false;
EPartialState m_partialState = psNone;
uint32 m_crc = 0;
CString m_hash16k;
CString m_parSetId;
static int m_idGen;
static int m_idMax;
@@ -249,18 +256,27 @@ public:
cfFailure
};
CompletedFile(int id, const char* fileName, EStatus status, uint32 crc);
CompletedFile(int id, const char* filename, EStatus status, uint32 crc,
bool parFile, const char* hash16k, const char* parSetId);
int GetId() { return m_id; }
void SetFileName(const char* fileName) { m_fileName = fileName; }
const char* GetFileName() { return m_fileName; }
void SetFilename(const char* filename) { m_filename = filename; }
const char* GetFilename() { return m_filename; }
bool GetParFile() { return m_parFile; }
EStatus GetStatus() { return m_status; }
uint32 GetCrc() { return m_crc; }
const char* GetHash16k() { return m_hash16k; }
void SetHash16k(const char* hash16k) { m_hash16k = hash16k; }
const char* GetParSetId() { return m_parSetId; }
void SetParSetId(const char* parSetId) { m_parSetId = parSetId; }
private:
int m_id;
CString m_fileName;
CString m_filename;
EStatus m_status;
uint32 m_crc;
bool m_parFile;
CString m_hash16k;
CString m_parSetId;
};
typedef std::deque<CompletedFile> CompletedFileList;
@@ -332,11 +348,19 @@ enum EDupeMode
class NzbInfo
{
public:
enum ERenameStatus
enum EDirectRenameStatus
{
tsNone,
tsRunning,
tsFailure,
tsSuccess
};
enum EPostRenameStatus
{
rsNone,
rsSkipped,
rsFailure,
rsNothing,
rsSuccess
};
@@ -350,7 +374,15 @@ public:
psManual
};
enum EUnpackStatus
enum EDirectUnpackStatus
{
nsNone,
nsRunning,
nsFailure,
nsSuccess
};
enum EPostUnpackStatus
{
usNone,
usSkipped,
@@ -485,12 +517,18 @@ public:
void BuildDestDirName();
CString BuildFinalDirName();
CompletedFileList* GetCompletedFiles() { return &m_completedFiles; }
ERenameStatus GetRenameStatus() { return m_renameStatus; }
void SetRenameStatus(ERenameStatus renameStatus) { m_renameStatus = renameStatus; }
void SetDirectRenameStatus(EDirectRenameStatus renameStatus) { m_directRenameStatus = renameStatus; }
EDirectRenameStatus GetDirectRenameStatus() { return m_directRenameStatus; }
EPostRenameStatus GetParRenameStatus() { return m_parRenameStatus; }
void SetParRenameStatus(EPostRenameStatus renameStatus) { m_parRenameStatus = renameStatus; }
EPostRenameStatus GetRarRenameStatus() { return m_rarRenameStatus; }
void SetRarRenameStatus(EPostRenameStatus renameStatus) { m_rarRenameStatus = renameStatus; }
EParStatus GetParStatus() { return m_parStatus; }
void SetParStatus(EParStatus parStatus) { m_parStatus = parStatus; }
EUnpackStatus GetUnpackStatus() { return m_unpackStatus; }
void SetUnpackStatus(EUnpackStatus unpackStatus) { m_unpackStatus = unpackStatus; }
EDirectUnpackStatus GetDirectUnpackStatus() { return m_directUnpackStatus; }
void SetDirectUnpackStatus(EDirectUnpackStatus directUnpackStatus) { m_directUnpackStatus = directUnpackStatus; }
EPostUnpackStatus GetUnpackStatus() { return m_unpackStatus; }
void SetUnpackStatus(EPostUnpackStatus unpackStatus) { m_unpackStatus = unpackStatus; }
ECleanupStatus GetCleanupStatus() { return m_cleanupStatus; }
void SetCleanupStatus(ECleanupStatus cleanupStatus) { m_cleanupStatus = cleanupStatus; }
EMoveStatus GetMoveStatus() { return m_moveStatus; }
@@ -554,6 +592,8 @@ public:
void SetUnpackSec(int unpackSec) { m_unpackSec = unpackSec; }
time_t GetDownloadStartTime() { return m_downloadStartTime; }
void SetDownloadStartTime(time_t downloadStartTime) { m_downloadStartTime = downloadStartTime; }
bool GetChanged() { return m_changed; }
void SetChanged(bool changed) { m_changed = changed; }
void SetReprocess(bool reprocess) { m_reprocess = reprocess; }
bool GetReprocess() { return m_reprocess; }
time_t GetQueueScriptTime() { return m_queueScriptTime; }
@@ -575,9 +615,18 @@ public:
void SetMessageCount(int messageCount) { m_messageCount = messageCount; }
int GetCachedMessageCount() { return m_cachedMessageCount; }
GuardedMessageList GuardCachedMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
bool GetAllFirst() { return m_allFirst; }
void SetAllFirst(bool allFirst) { m_allFirst = allFirst; }
bool GetWaitingPar() { return m_waitingPar; }
void SetWaitingPar(bool waitingPar) { m_waitingPar = waitingPar; }
bool GetLoadingPar() { return m_loadingPar; }
void SetLoadingPar(bool loadingPar) { m_loadingPar = loadingPar; }
Thread* GetUnpackThread() { return m_unpackThread; }
void SetUnpackThread(Thread* unpackThread) { m_unpackThread = unpackThread; }
void UpdateCurrentStats();
void UpdateCompletedStats(FileInfo* fileInfo);
void UpdateDeletedStats(FileInfo* fileInfo);
bool IsDownloadCompleted(bool ignorePausedPars);
static const int FORCE_PRIORITY = 900;
@@ -616,9 +665,12 @@ private:
time_t m_maxTime = 0;
int m_priority = 0;
CompletedFileList m_completedFiles;
ERenameStatus m_renameStatus = rsNone;
EDirectRenameStatus m_directRenameStatus = tsNone;
EPostRenameStatus m_parRenameStatus = rsNone;
EPostRenameStatus m_rarRenameStatus = rsNone;
EParStatus m_parStatus = psNone;
EUnpackStatus m_unpackStatus = usNone;
EDirectUnpackStatus m_directUnpackStatus = nsNone;
EPostUnpackStatus m_unpackStatus = usNone;
ECleanupStatus m_cleanupStatus = csNone;
EMoveStatus m_moveStatus = msNone;
EDeleteStatus m_deleteStatus = dsNone;
@@ -652,17 +704,23 @@ private:
std::unique_ptr<PostInfo> m_postInfo;
int64 m_downloadedSize = 0;
time_t m_downloadStartTime = 0;
int m_downloadStartSec = 0;
int m_downloadSec = 0;
int m_postTotalSec = 0;
int m_parSec = 0;
int m_repairSec = 0;
int m_unpackSec = 0;
bool m_reprocess = false;
bool m_changed = false;
time_t m_queueScriptTime = 0;
bool m_parFull = false;
int m_messageCount = 0;
int m_cachedMessageCount = 0;
int m_feedId = 0;
bool m_allFirst = false;
bool m_waitingPar = false;
bool m_loadingPar = false;
Thread* m_unpackThread = nullptr;
static int m_idGen;
static int m_idMax;
@@ -685,14 +743,17 @@ public:
ptVerifyingSources,
ptRepairing,
ptVerifyingRepaired,
ptRenaming,
ptParRenaming,
ptRarRenaming,
ptUnpacking,
ptCleaningUp,
ptMoving,
ptExecutingScript,
ptFinished
};
typedef std::vector<CString> ParredFiles;
typedef std::vector<CString> ExtractedArchives;
NzbInfo* GetNzbInfo() { return m_nzbInfo; }
void SetNzbInfo(NzbInfo* nzbInfo) { m_nzbInfo = nzbInfo; }
@@ -726,9 +787,12 @@ public:
void SetPassListTried(bool passListTried) { m_passListTried = passListTried; }
int GetLastUnpackStatus() { return m_lastUnpackStatus; }
void SetLastUnpackStatus(int unpackStatus) { m_lastUnpackStatus = unpackStatus; }
bool GetNeedParCheck() { return m_needParCheck; }
void SetNeedParCheck(bool needParCheck) { m_needParCheck = needParCheck; }
Thread* GetPostThread() { return m_postThread; }
void SetPostThread(Thread* postThread) { m_postThread = postThread; }
ParredFiles* GetParredFiles() { return &m_parredFiles; }
ExtractedArchives* GetExtractedArchives() { return &m_extractedArchives; }
private:
NzbInfo* m_nzbInfo = nullptr;
@@ -741,6 +805,7 @@ private:
bool m_unpackTried = false;
bool m_passListTried = false;
int m_lastUnpackStatus = 0;
bool m_needParCheck = false;
EStage m_stage = ptQueued;
CString m_progressLabel = "";
int m_fileProgress = 0;
@@ -749,6 +814,7 @@ private:
time_t m_stageTime = 0;
Thread* m_postThread = nullptr;
ParredFiles m_parredFiles;
ExtractedArchives m_extractedArchives;
};
typedef std::vector<int> IdList;
@@ -842,6 +908,7 @@ public:
eaNzbFound,
eaNzbAdded,
eaNzbDeleted,
eaNzbNamed,
eaFileCompleted,
eaFileDeleted,
eaUrlCompleted
@@ -864,12 +931,14 @@ public:
eaFileResume, // resume (unpause) files
eaFileDelete, // delete files
eaFilePauseAllPars, // pause only (all) pars (does not affect other files)
eaFilePauseExtraPars, // pause only (almost all) pars, except main par-file (does not affect other files)
eaFilePauseExtraPars, // pause (almost all) pars, except main par-file (does not affect other files)
eaFileReorder, // set file order
eaFileSplit, // split - create new group from selected files
eaGroupMoveOffset, // move group to m_iOffset relative to the current position in download-queue
eaGroupMoveOffset, // move group to offset relative to the current position in download-queue
eaGroupMoveTop, // move group to the top of download-queue
eaGroupMoveBottom, // move group to the bottom of download-queue
eaGroupMoveBefore, // move group to a certain position
eaGroupMoveAfter, // move group to a certain position
eaGroupPause, // pause group
eaGroupResume, // resume (unpause) group
eaGroupDelete, // delete group and put to history, delete already downloaded files
@@ -888,6 +957,7 @@ public:
eaGroupSetDupeScore, // set duplicate score
eaGroupSetDupeMode, // set duplicate mode
eaGroupSort, // sort groups
eaGroupSortFiles, // sort files for optimal download order
eaPostDelete, // cancel post-processing
eaHistoryDelete, // hide history-item
eaHistoryFinalDelete, // delete history-item
@@ -918,8 +988,8 @@ public:
static GuardedDownloadQueue Guard() { return GuardedDownloadQueue(g_DownloadQueue, &g_DownloadQueue->m_lockMutex); }
NzbList* GetQueue() { return &m_queue; }
HistoryList* GetHistory() { return &m_history; }
virtual bool EditEntry(int ID, EEditAction action, int offset, const char* text) = 0;
virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, int offset, const char* text) = 0;
virtual bool EditEntry(int ID, EEditAction action, const char* args) = 0;
virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, const char* args) = 0;
virtual void HistoryChanged() = 0;
virtual void Save() = 0;
void CalcRemainingSize(int64* remaining, int64* remainingForced);

View File

@@ -287,7 +287,7 @@ void DupeCoordinator::NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
info("Moving collection %s with lower duplicate score to history", queuedNzbInfo->GetName());
queuedNzbInfo->SetDeleteStatus(NzbInfo::dsDupe);
downloadQueue->EditEntry(queuedNzbInfo->GetId(),
DownloadQueue::eaGroupDelete, 0, nullptr);
DownloadQueue::eaGroupDelete, nullptr);
it = downloadQueue->GetQueue()->begin() + index;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -130,8 +130,8 @@ void HistoryCoordinator::AddToHistory(DownloadQueue* downloadQueue, NzbInfo* nzb
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
nzbInfo->UpdateCompletedStats(fileInfo);
nzbInfo->GetCompletedFiles()->emplace_back(fileInfo->GetId(),
fileInfo->GetFilename(), CompletedFile::cfNone, 0);
nzbInfo->GetCompletedFiles()->emplace_back(fileInfo->GetId(), fileInfo->GetFilename(),
CompletedFile::cfNone, 0, fileInfo->GetParFile(), fileInfo->GetHash16k(), fileInfo->GetParSetId());
}
// Cleaning up parked files if par-check was successful or unpack was successful or
@@ -169,7 +169,7 @@ void HistoryCoordinator::AddToHistory(DownloadQueue* downloadQueue, NzbInfo* nzb
(completedFile.GetStatus() == CompletedFile::cfPartial &&
&completedFile == &*nzbInfo->GetCompletedFiles()->rbegin()))
{
nzbInfo->PrintMessage(Message::mkDetail, "Parking file %s", completedFile.GetFileName());
nzbInfo->PrintMessage(Message::mkDetail, "Parking file %s", completedFile.GetFilename());
nzbInfo->SetParkedFileCount(nzbInfo->GetParkedFileCount() + 1);
}
}
@@ -178,6 +178,11 @@ void HistoryCoordinator::AddToHistory(DownloadQueue* downloadQueue, NzbInfo* nzb
nzbInfo->SetRemainingParCount(0);
nzbInfo->SetParking(false);
if (nzbInfo->GetDirectRenameStatus() == NzbInfo::tsRunning)
{
nzbInfo->SetDirectRenameStatus(NzbInfo::tsFailure);
}
nzbInfo->PrintMessage(Message::mkInfo, "Collection %s added to history", nzbInfo->GetName());
}
@@ -232,7 +237,8 @@ void HistoryCoordinator::PrepareEdit(DownloadQueue* downloadQueue, IdList* idLis
}
}
bool HistoryCoordinator::EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
bool HistoryCoordinator::EditList(DownloadQueue* downloadQueue, IdList* idList,
DownloadQueue::EEditAction action, const char* args)
{
bool ok = false;
PrepareEdit(downloadQueue, idList, action);
@@ -270,22 +276,22 @@ bool HistoryCoordinator::EditList(DownloadQueue* downloadQueue, IdList* idList,
break;
case DownloadQueue::eaHistorySetParameter:
ok = HistorySetParameter(historyInfo, text);
ok = HistorySetParameter(historyInfo, args);
break;
case DownloadQueue::eaHistorySetCategory:
ok = HistorySetCategory(historyInfo, text);
ok = HistorySetCategory(historyInfo, args);
break;
case DownloadQueue::eaHistorySetName:
ok = HistorySetName(historyInfo, text);
ok = HistorySetName(historyInfo, args);
break;
case DownloadQueue::eaHistorySetDupeKey:
case DownloadQueue::eaHistorySetDupeScore:
case DownloadQueue::eaHistorySetDupeMode:
case DownloadQueue::eaHistorySetDupeBackup:
HistorySetDupeParam(historyInfo, action, text);
HistorySetDupeParam(historyInfo, action, args);
break;
case DownloadQueue::eaHistoryMarkBad:
@@ -378,8 +384,10 @@ void HistoryCoordinator::MoveToQueue(DownloadQueue* downloadQueue, HistoryList::
if (!nzbInfo->GetUnpackCleanedUpDisk())
{
nzbInfo->SetUnpackStatus(NzbInfo::usNone);
nzbInfo->SetDirectUnpackStatus(NzbInfo::nsNone);
nzbInfo->SetCleanupStatus(NzbInfo::csNone);
nzbInfo->SetRenameStatus(NzbInfo::rsNone);
nzbInfo->SetParRenameStatus(NzbInfo::rsNone);
nzbInfo->SetRarRenameStatus(NzbInfo::rsNone);
nzbInfo->SetPostTotalSec(nzbInfo->GetPostTotalSec() - nzbInfo->GetUnpackSec());
nzbInfo->SetUnpackSec(0);
@@ -489,7 +497,10 @@ void HistoryCoordinator::HistoryRedownload(DownloadQueue* downloadQueue, History
nzbInfo->SetMoveStatus(NzbInfo::msNone);
nzbInfo->SetUnpackCleanedUpDisk(false);
nzbInfo->SetParStatus(NzbInfo::psNone);
nzbInfo->SetRenameStatus(NzbInfo::rsNone);
nzbInfo->SetParRenameStatus(NzbInfo::rsNone);
nzbInfo->SetRarRenameStatus(NzbInfo::rsNone);
nzbInfo->SetDirectRenameStatus(NzbInfo::tsNone);
nzbInfo->SetDirectUnpackStatus(NzbInfo::nsNone);
nzbInfo->SetDownloadedSize(0);
nzbInfo->SetDownloadSec(0);
nzbInfo->SetPostTotalSec(0);
@@ -497,6 +508,9 @@ void HistoryCoordinator::HistoryRedownload(DownloadQueue* downloadQueue, History
nzbInfo->SetRepairSec(0);
nzbInfo->SetUnpackSec(0);
nzbInfo->SetExtraParBlocks(0);
nzbInfo->SetAllFirst(false);
nzbInfo->SetWaitingPar(false);
nzbInfo->SetLoadingPar(false);
nzbInfo->GetCompletedFiles()->clear();
nzbInfo->GetServerStats()->clear();
nzbInfo->GetCurrentServerStats()->clear();
@@ -584,7 +598,7 @@ void HistoryCoordinator::HistoryRetry(DownloadQueue* downloadQueue, HistoryList:
g_DiskState->LoadFileState(fileInfo.get(), g_ServerPool->GetServers(), true) &&
(resetFailed || fileInfo->GetRemainingSize() > 0))))
{
fileInfo->SetFilename(completedFile.GetFileName());
fileInfo->SetFilename(completedFile.GetFilename());
fileInfo->SetNzbInfo(nzbInfo);
BString<1024> outputFilename("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, fileInfo->GetFilename());
@@ -631,7 +645,7 @@ void HistoryCoordinator::HistoryRetry(DownloadQueue* downloadQueue, HistoryList:
if (g_Options->GetParCheck() != Options::pcForce)
{
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseExtraPars, 0, nullptr);
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseExtraPars, nullptr);
}
}

View File

@@ -28,7 +28,7 @@ class HistoryCoordinator : public Service
{
public:
void AddToHistory(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text);
bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, const char* args);
void DeleteDiskFiles(NzbInfo* nzbInfo);
void HistoryHide(DownloadQueue* downloadQueue, HistoryInfo* historyInfo, int rindex);
void Redownload(DownloadQueue* downloadQueue, HistoryInfo* historyInfo);

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,16 +32,16 @@
#include "StatMeter.h"
bool QueueCoordinator::CoordinatorDownloadQueue::EditEntry(
int ID, EEditAction action, int offset, const char* text)
int ID, EEditAction action, const char* args)
{
return m_owner->m_queueEditor.EditEntry(&m_owner->m_downloadQueue, ID, action, offset, text);
return m_owner->m_queueEditor.EditEntry(&m_owner->m_downloadQueue, ID, action, args);
}
bool QueueCoordinator::CoordinatorDownloadQueue::EditList(
IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, int offset, const char* text)
IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, const char* args)
{
m_massEdit = true;
bool ret = m_owner->m_queueEditor.EditList(&m_owner->m_downloadQueue, idList, nameList, matchMode, action, offset, text);
bool ret = m_owner->m_queueEditor.EditList(&m_owner->m_downloadQueue, idList, nameList, matchMode, action, args);
m_massEdit = false;
if (m_wantSave)
{
@@ -63,6 +63,11 @@ void QueueCoordinator::CoordinatorDownloadQueue::Save()
g_DiskState->SaveDownloadQueue(this, m_historyChanged);
}
for (NzbInfo* nzbInfo : GetQueue())
{
nzbInfo->SetChanged(false);
}
m_wantSave = false;
m_historyChanged = false;
}
@@ -71,7 +76,6 @@ QueueCoordinator::QueueCoordinator()
{
debug("Creating QueueCoordinator");
m_downloadQueue.m_owner = this;
CoordinatorDownloadQueue::Init(&m_downloadQueue);
}
@@ -225,7 +229,7 @@ void QueueCoordinator::Run()
wasStandBy = standBy;
if (standBy)
{
SavePartialState();
SaveAllPartialState();
}
}
@@ -248,7 +252,7 @@ void QueueCoordinator::Run()
ResetHangingDownloads();
if (!standBy)
{
SavePartialState();
SaveAllPartialState();
}
resetCounter = 0;
g_StatMeter->IntervalCheck();
@@ -256,23 +260,31 @@ void QueueCoordinator::Run()
}
}
WaitJobs();
SaveAllPartialState();
debug("Exiting QueueCoordinator-loop");
}
void QueueCoordinator::WaitJobs()
{
// waiting for downloads
debug("QueueCoordinator: waiting for Downloads to complete");
bool completed = false;
while (!completed)
while (true)
{
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
completed = m_activeDownloads.size() == 0;
if (m_activeDownloads.empty())
{
break;
}
}
usleep(100 * 1000);
ResetHangingDownloads();
}
debug("QueueCoordinator: Downloads are completed");
SavePartialState();
debug("Exiting QueueCoordinator-loop");
}
/*
@@ -371,7 +383,10 @@ NzbInfo* QueueCoordinator::AddNzbFileToQueue(std::unique_ptr<NzbInfo> nzbInfo, N
{
// in a case if none of listeners did already delete the temporary object - we do it ourselves
downloadQueue->GetQueue()->Remove(addedNzb);
addedNzb = nullptr;
if (!downloadQueue->GetHistory()->Find(addedNzb->GetId()))
{
addedNzb = nullptr;
}
}
downloadQueue->Save();
@@ -489,6 +504,14 @@ bool QueueCoordinator::GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &f
break;
}
if (g_Options->GetDirectRename() &&
fileInfo->GetNzbInfo()->GetDirectRenameStatus() <= NzbInfo::tsRunning &&
!fileInfo->GetNzbInfo()->GetAllFirst() &&
GetNextFirstArticle(fileInfo->GetNzbInfo(), fileInfo, articleInfo))
{
return true;
}
if (fileInfo->GetArticles()->empty() && g_Options->GetSaveQueue() && g_Options->GetServerMode())
{
g_DiskState->LoadArticles(fileInfo);
@@ -501,8 +524,7 @@ bool QueueCoordinator::GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &f
if (article->GetStatus() == ArticleInfo::aiUndefined)
{
articleInfo = article;
ok = true;
break;
return true;
}
}
@@ -514,7 +536,39 @@ bool QueueCoordinator::GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &f
}
}
return ok;
return false;
}
bool QueueCoordinator::GetNextFirstArticle(NzbInfo* nzbInfo, FileInfo* &fileInfo, ArticleInfo* &articleInfo)
{
// find a file not renamed yet
for (FileInfo* fileInfo1 : nzbInfo->GetFileList())
{
if (!fileInfo1->GetFilenameConfirmed())
{
if (fileInfo1->GetArticles()->empty() && g_Options->GetSaveQueue() && g_Options->GetServerMode())
{
g_DiskState->LoadArticles(fileInfo1);
LoadPartialState(fileInfo1);
}
if (!fileInfo1->GetArticles()->empty())
{
ArticleInfo* article = fileInfo1->GetArticles()->at(0).get();
if (article->GetStatus() == ArticleInfo::aiUndefined)
{
fileInfo = fileInfo1;
articleInfo = article;
nzbInfo->SetDirectRenameStatus(NzbInfo::tsRunning);
return true;
}
}
}
}
// no more files for renaming remained
nzbInfo->SetAllFirst(true);
return false;
}
void QueueCoordinator::StartArticleDownload(FileInfo* fileInfo, ArticleInfo* articleInfo, NntpConnection* connection)
@@ -528,7 +582,12 @@ void QueueCoordinator::StartArticleDownload(FileInfo* fileInfo, ArticleInfo* art
articleDownloader->SetArticleInfo(articleInfo);
articleDownloader->SetConnection(connection);
BString<1024> infoName("%s%c%s [%i/%i]", fileInfo->GetNzbInfo()->GetName(), (int)PATH_SEPARATOR, fileInfo->GetFilename(), articleInfo->GetPartNumber(), (int)fileInfo->GetArticles()->size());
if (articleInfo->GetPartNumber() == 1 && g_Options->GetDirectRename() && g_Options->GetDecode())
{
articleDownloader->SetContentAnalyzer(m_directRenamer.MakeArticleContentAnalyzer());
}
BString<1024> infoName("%s%c%s [%i/%i]", fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR, fileInfo->GetFilename(), articleInfo->GetPartNumber(), (int)fileInfo->GetArticles()->size());
articleDownloader->SetInfoName(infoName);
articleInfo->SetStatus(ArticleInfo::aiRunning);
@@ -557,12 +616,14 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
debug("Article downloaded");
FileInfo* fileInfo = articleDownloader->GetFileInfo();
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
ArticleInfo* articleInfo = articleDownloader->GetArticleInfo();
bool retry = false;
bool fileCompleted = false;
bool completeFileParts = false;
{
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
ArticleInfo* articleInfo = articleDownloader->GetArticleInfo();
bool retry = false;
bool fileCompleted = false;
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (articleDownloader->GetStatus() == ArticleDownloader::adFinished)
@@ -587,6 +648,10 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
{
articleInfo->SetStatus(ArticleInfo::aiUndefined);
retry = true;
if (articleInfo->GetPartNumber() == 1)
{
nzbInfo->SetAllFirst(false);
}
}
if (!retry)
@@ -608,13 +673,22 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
articleDownloader->GetStatus() == ArticleDownloader::adFinished &&
articleDownloader->GetArticleFilename())
{
fileInfo->SetFilename(articleDownloader->GetArticleFilename());
fileInfo->MakeValidFilename();
// in "FileNaming=auto"-mode prefer filename from nzb-file to filename read from article
// if the name from article seems to be obfuscated
bool useFilenameFromArticle = g_Options->GetFileNaming() == Options::nfArticle ||
(g_Options->GetFileNaming() == Options::nfAuto &&
!Util::AlphaNum(articleDownloader->GetArticleFilename()) &&
!nzbInfo->GetManyDupeFiles());
if (useFilenameFromArticle)
{
fileInfo->SetFilename(articleDownloader->GetArticleFilename());
fileInfo->MakeValidFilename();
}
fileInfo->SetFilenameConfirmed(true);
if (g_Options->GetDupeCheck() &&
nzbInfo->GetDupeMode() != dmForce &&
!nzbInfo->GetManyDupeFiles() &&
FileSystem::FileExists(nzbInfo->GetDestDir(), fileInfo->GetFilename()))
FileSystem::FileExists(BString<1024>("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, fileInfo->GetFilename())))
{
warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", fileInfo->GetFilename());
fileCompleted = false;
@@ -623,6 +697,11 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
}
}
if (articleDownloader->GetContentAnalyzer() && articleDownloader->GetStatus() == ArticleDownloader::adFinished)
{
m_directRenamer.ArticleDownloaded(downloadQueue, fileInfo, articleInfo, articleDownloader->GetContentAnalyzer());
}
nzbInfo->SetDownloadedSize(nzbInfo->GetDownloadedSize() + articleDownloader->GetDownloadedSize());
CheckHealth(downloadQueue, fileInfo);
@@ -631,35 +710,44 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
{
fileCompleted = true;
}
completeFileParts = fileCompleted && (!fileInfo->GetDeleted() || nzbInfo->GetParking());
if (!completeFileParts)
{
DeleteDownloader(downloadQueue, articleDownloader, false);
}
}
bool deleteFileObj = false;
if (fileCompleted && (!fileInfo->GetDeleted() || nzbInfo->GetParking()))
if (completeFileParts)
{
// all jobs done
articleDownloader->CompleteFileParts();
fileInfo->SetPartialChanged(false);
deleteFileObj = true;
}
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
DeleteDownloader(downloadQueue, articleDownloader, true);
}
}
bool hasOtherDownloaders = fileInfo->GetActiveDownloads() > 1;
deleteFileObj |= fileInfo->GetDeleted() && !hasOtherDownloaders;
void QueueCoordinator::DeleteDownloader(DownloadQueue* downloadQueue,
ArticleDownloader* articleDownloader, bool fileCompleted)
{
FileInfo* fileInfo = articleDownloader->GetFileInfo();
NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
bool hasOtherDownloaders = fileInfo->GetActiveDownloads() > 1;
bool deleteFileObj = fileCompleted || (fileInfo->GetDeleted() && !hasOtherDownloaders);
// remove downloader from downloader list
m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), articleDownloader));
// remove downloader from downloader list
m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), articleDownloader));
fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() - 1);
nzbInfo->SetActiveDownloads(nzbInfo->GetActiveDownloads() - 1);
fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() - 1);
nzbInfo->SetActiveDownloads(nzbInfo->GetActiveDownloads() - 1);
if (deleteFileObj)
{
DeleteFileInfo(downloadQueue, fileInfo, fileCompleted);
downloadQueue->Save();
}
if (deleteFileObj)
{
DeleteFileInfo(downloadQueue, fileInfo, fileCompleted);
downloadQueue->Save();
}
}
@@ -712,7 +800,19 @@ void QueueCoordinator::DeleteFileInfo(DownloadQueue* downloadQueue, FileInfo* fi
completed && fileInfo->GetOutputFilename() ?
FileSystem::BaseFileName(fileInfo->GetOutputFilename()) : fileInfo->GetFilename(),
fileStatus,
fileStatus == CompletedFile::cfSuccess ? fileInfo->GetCrc() : 0);
fileStatus == CompletedFile::cfSuccess ? fileInfo->GetCrc() : 0,
fileInfo->GetParFile(), fileInfo->GetHash16k(), fileInfo->GetParSetId());
}
if (g_Options->GetDirectRename())
{
m_directRenamer.FileDownloaded(downloadQueue, fileInfo);
}
if (nzbInfo->GetDirectRenameStatus() == NzbInfo::tsRunning &&
!nzbInfo->GetDeleting() && nzbInfo->IsDownloadCompleted(true))
{
DiscardDirectRename(downloadQueue, nzbInfo);
}
std::unique_ptr<FileInfo> srcFileInfo = nzbInfo->GetFileList()->Remove(fileInfo);
@@ -745,30 +845,45 @@ void QueueCoordinator::DiscardTempFiles(FileInfo* fileInfo)
}
}
void QueueCoordinator::SavePartialState()
void QueueCoordinator::SaveAllPartialState()
{
if (!(g_Options->GetServerMode() && g_Options->GetSaveQueue() && g_Options->GetContinuePartial()))
if (!(g_Options->GetServerMode() && g_Options->GetSaveQueue()))
{
return;
}
bool hasUnsavedData = false;
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
for (FileInfo* fileInfo : nzbInfo->GetFileList())
if (g_Options->GetContinuePartial())
{
if (fileInfo->GetPartialChanged())
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
debug("Saving partial state for %s", fileInfo->GetFilename());
if (fileInfo->GetPartialState() == FileInfo::psCompleted)
{
g_DiskState->DiscardFile(fileInfo->GetId(), false, false, true);
}
g_DiskState->SaveFileState(fileInfo, false);
fileInfo->SetPartialChanged(false);
fileInfo->SetPartialState(FileInfo::psPartial);
SavePartialState(fileInfo);
}
}
hasUnsavedData |= nzbInfo->GetChanged();
}
if (hasUnsavedData)
{
downloadQueue->Save();
}
}
void QueueCoordinator::SavePartialState(FileInfo* fileInfo)
{
if (fileInfo->GetPartialChanged())
{
debug("Saving partial state for %s", fileInfo->GetFilename());
if (fileInfo->GetPartialState() == FileInfo::psCompleted)
{
g_DiskState->DiscardFile(fileInfo->GetId(), false, false, true);
}
g_DiskState->SaveFileState(fileInfo, false);
fileInfo->SetPartialChanged(false);
fileInfo->SetPartialState(FileInfo::psPartial);
}
}
@@ -808,7 +923,7 @@ void QueueCoordinator::CheckHealth(DownloadQueue* downloadQueue, FileInfo* fileI
warn("Pausing %s due to health %.1f%% below critical %.1f%%", fileInfo->GetNzbInfo()->GetName(),
fileInfo->GetNzbInfo()->CalcHealth() / 10.0, fileInfo->GetNzbInfo()->CalcCriticalHealth(true) / 10.0);
fileInfo->GetNzbInfo()->SetHealthPaused(true);
downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPause, 0, nullptr);
downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPause, nullptr);
}
else if (g_Options->GetHealthCheck() == Options::hcDelete ||
g_Options->GetHealthCheck() == Options::hcPark)
@@ -820,7 +935,7 @@ void QueueCoordinator::CheckHealth(DownloadQueue* downloadQueue, FileInfo* fileI
fileInfo->GetNzbInfo()->SetDeleteStatus(NzbInfo::dsHealth);
downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(),
g_Options->GetHealthCheck() == Options::hcPark ? DownloadQueue::eaGroupParkDelete : DownloadQueue::eaGroupDelete,
0, nullptr);
nullptr);
}
}
@@ -1172,3 +1287,110 @@ bool QueueCoordinator::SplitQueueEntries(DownloadQueue* downloadQueue, RawFileLi
return true;
}
void QueueCoordinator::DirectRenameCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if (g_Options->GetSaveQueue() && g_Options->GetServerMode() && !fileInfo->GetArticles()->empty())
{
// save new file name into disk state file
g_DiskState->SaveFile(fileInfo);
}
}
DiscardDirectRename(downloadQueue, nzbInfo);
nzbInfo->SetDirectRenameStatus(NzbInfo::tsSuccess);
if (g_Options->GetParCheck() != Options::pcForce)
{
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupResume, nullptr);
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseAllPars, nullptr);
}
if (g_Options->GetReorderFiles())
{
nzbInfo->PrintMessage(Message::mkInfo, "Reordering files for %s", nzbInfo->GetName());
downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupSortFiles, nullptr);
}
downloadQueue->Save();
DownloadQueue::Aspect namedAspect = { DownloadQueue::eaNzbNamed, downloadQueue, nzbInfo, nullptr };
downloadQueue->Notify(&namedAspect);
}
void QueueCoordinator::DiscardDirectRename(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
int64 discardedSize = 0;
int discardedCount = 0;
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if (fileInfo->GetParFile() && fileInfo->GetCompletedArticles() == 1 && fileInfo->GetActiveDownloads() == 0)
{
// discard downloaded articles from partially downloaded par-files
discardedSize += fileInfo->GetSuccessSize();
discardedCount++;
nzbInfo->SetCurrentSuccessArticles(nzbInfo->GetCurrentSuccessArticles() - fileInfo->GetSuccessArticles());
nzbInfo->SetCurrentSuccessSize(nzbInfo->GetCurrentSuccessSize() - fileInfo->GetSuccessSize());
nzbInfo->SetParCurrentSuccessSize(nzbInfo->GetParCurrentSuccessSize() - fileInfo->GetSuccessSize());
fileInfo->SetSuccessSize(0);
fileInfo->SetSuccessArticles(0);
nzbInfo->SetCurrentFailedArticles(nzbInfo->GetCurrentFailedArticles() - fileInfo->GetFailedArticles());
nzbInfo->SetCurrentFailedSize(nzbInfo->GetCurrentFailedSize() - fileInfo->GetFailedSize());
nzbInfo->SetParCurrentFailedSize(nzbInfo->GetParCurrentFailedSize() - fileInfo->GetFailedSize());
fileInfo->SetFailedSize(0);
fileInfo->SetFailedArticles(0);
fileInfo->SetCompletedArticles(0);
fileInfo->SetRemainingSize(fileInfo->GetSize() - fileInfo->GetMissedSize());
// discard temporary files
DiscardTempFiles(fileInfo);
g_DiskState->DiscardFile(fileInfo->GetId(), false, true, false);
fileInfo->SetOutputFilename(nullptr);
fileInfo->SetOutputInitialized(false);
fileInfo->SetCachedArticles(0);
fileInfo->SetPartialChanged(false);
fileInfo->SetPartialState(FileInfo::psNone);
if (g_Options->GetSaveQueue() && g_Options->GetServerMode())
{
// free up memory used by articles if possible
fileInfo->GetArticles()->clear();
}
else
{
// reset article states if discarding isn't possible
for (ArticleInfo* articleInfo : fileInfo->GetArticles())
{
articleInfo->SetStatus(ArticleInfo::aiUndefined);
articleInfo->SetResultFilename(nullptr);
articleInfo->DiscardSegment();
}
}
}
if (g_Options->GetSaveQueue() && g_Options->GetServerMode() &&
!fileInfo->GetArticles()->empty() && g_Options->GetContinuePartial() &&
fileInfo->GetActiveDownloads() == 0 && fileInfo->GetCachedArticles() == 0)
{
// discard article infos to free up memory if possible
debug("Discarding article infos for %s/%s", nzbInfo->GetName(), fileInfo->GetFilename());
fileInfo->SetPartialChanged(true);
SavePartialState(fileInfo);
fileInfo->GetArticles()->clear();
}
}
if (discardedSize > 0)
{
nzbInfo->PrintMessage(Message::mkDetail, "Discarded %s from %i files used for direct renaming",
*Util::FormatSize(discardedSize), discardedCount);
}
}

View File

@@ -2,7 +2,7 @@
* 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>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@
#include "Observer.h"
#include "QueueEditor.h"
#include "NntpConnection.h"
#include "DirectRenamer.h"
class QueueCoordinator : public Thread, public Observer, public Debuggable
{
@@ -60,9 +61,10 @@ private:
class CoordinatorDownloadQueue : public DownloadQueue
{
public:
virtual bool EditEntry(int ID, EEditAction action, int offset, const char* text);
CoordinatorDownloadQueue(QueueCoordinator* owner) : m_owner(owner) {}
virtual bool EditEntry(int ID, EEditAction action, const char* args);
virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode,
EEditAction action, int offset, const char* text);
EEditAction action, const char* args);
virtual void HistoryChanged() { m_historyChanged = true; }
virtual void Save();
private:
@@ -73,23 +75,41 @@ private:
friend class QueueCoordinator;
};
CoordinatorDownloadQueue m_downloadQueue;
class CoordinatorDirectRenamer : public DirectRenamer
{
public:
CoordinatorDirectRenamer(QueueCoordinator* owner) : m_owner(owner) {}
protected:
virtual void RenameCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{ m_owner->DirectRenameCompleted(downloadQueue, nzbInfo); }
private:
QueueCoordinator* m_owner;
};
CoordinatorDownloadQueue m_downloadQueue{this};
ActiveDownloads m_activeDownloads;
QueueEditor m_queueEditor;
CoordinatorDirectRenamer m_directRenamer{this};
bool m_hasMoreJobs = true;
int m_downloadsLimit;
int m_serverConfigGeneration = 0;
bool GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &fileInfo, ArticleInfo* &articleInfo);
bool GetNextFirstArticle(NzbInfo* nzbInfo, FileInfo* &fileInfo, ArticleInfo* &articleInfo);
void StartArticleDownload(FileInfo* fileInfo, ArticleInfo* articleInfo, NntpConnection* connection);
void ArticleCompleted(ArticleDownloader* articleDownloader);
void DeleteDownloader(DownloadQueue* downloadQueue, ArticleDownloader* articleDownloader, bool fileCompleted);
void DeleteFileInfo(DownloadQueue* downloadQueue, FileInfo* fileInfo, bool completed);
void DirectRenameCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void DiscardDirectRename(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
void CheckHealth(DownloadQueue* downloadQueue, FileInfo* fileInfo);
void ResetHangingDownloads();
void AdjustDownloadsLimit();
void Load();
void SavePartialState();
void SaveAllPartialState();
void SavePartialState(FileInfo* fileInfo);
void LoadPartialState(FileInfo* fileInfo);
void WaitJobs();
};
extern QueueCoordinator* g_QueueCoordinator;

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,6 +29,7 @@
#include "PrePostProcessor.h"
#include "HistoryCoordinator.h"
#include "UrlCoordinator.h"
#include "ParParser.h"
const int MAX_ID = 1000000000;
@@ -62,8 +63,6 @@ private:
QueueEditor::ItemList* m_sortItemList;
ESortCriteria m_sortCriteria;
ESortOrder m_sortOrder;
void AlignSelectedGroups();
};
bool GroupSorter::Execute(const char* sort)
@@ -112,8 +111,6 @@ bool GroupSorter::Execute(const char* sort)
m_sortOrder = soAuto;
}
AlignSelectedGroups();
RawNzbList tempList;
for (NzbInfo* nzbInfo : m_nzbList)
{
@@ -126,7 +123,7 @@ bool GroupSorter::Execute(const char* sort)
m_sortOrder = soDescending;
}
std::sort(m_nzbList->begin(), m_nzbList->end(), *this);
std::stable_sort(m_nzbList->begin(), m_nzbList->end(), *this);
if (origSortOrder == soAuto &&
std::equal(tempList.begin(), tempList.end(), m_nzbList->begin(),
@@ -136,7 +133,7 @@ bool GroupSorter::Execute(const char* sort)
}))
{
m_sortOrder = m_sortOrder == soDescending ? soAscending : soDescending;
std::sort(m_nzbList->begin(), m_nzbList->end(), *this);
std::stable_sort(m_nzbList->begin(), m_nzbList->end(), *this);
}
return true;
@@ -200,44 +197,6 @@ bool GroupSorter::operator()(const std::unique_ptr<NzbInfo>& refNzbInfo1, const
return ret;
}
void GroupSorter::AlignSelectedGroups()
{
NzbInfo* lastNzbInfo = nullptr;
uint32 lastNum = 0;
uint32 num = 0;
while (num < m_nzbList->size())
{
std::unique_ptr<NzbInfo>& nzbInfo = m_nzbList->at(num);
bool selected = false;
for (QueueEditor::EditItem& item : m_sortItemList)
{
if (item.m_nzbInfo == nzbInfo.get())
{
selected = true;
break;
}
}
if (selected)
{
if (lastNzbInfo && num - lastNum > 1)
{
std::unique_ptr<NzbInfo> movedNzbInfo = std::move(*(m_nzbList->begin() + num));
m_nzbList->erase(m_nzbList->begin() + num);
m_nzbList->insert(m_nzbList->begin() + lastNum + 1, std::move(movedNzbInfo));
lastNum++;
}
else
{
lastNum = num;
}
lastNzbInfo = nzbInfo.get();
}
num++;
}
}
FileInfo* QueueEditor::FindFileInfo(int id)
{
@@ -252,17 +211,11 @@ FileInfo* QueueEditor::FindFileInfo(int id)
return nullptr;
}
/*
* Set the pause flag of the specific entry in the queue
*/
void QueueEditor::PauseUnpauseEntry(FileInfo* fileInfo, bool pause)
{
fileInfo->SetPaused(pause);
}
/*
* Removes entry
*/
void QueueEditor::DeleteEntry(FileInfo* fileInfo)
{
if (!fileInfo->GetDeleted())
@@ -274,9 +227,6 @@ void QueueEditor::DeleteEntry(FileInfo* fileInfo)
}
}
/*
* Moves entry in the queue
*/
void QueueEditor::MoveEntry(FileInfo* fileInfo, int offset)
{
int entry = 0;
@@ -309,9 +259,6 @@ void QueueEditor::MoveEntry(FileInfo* fileInfo, int offset)
}
}
/*
* Moves group in the queue
*/
void QueueEditor::MoveGroup(NzbInfo* nzbInfo, int offset)
{
int entry = 0;
@@ -344,24 +291,24 @@ void QueueEditor::MoveGroup(NzbInfo* nzbInfo, int offset)
}
}
bool QueueEditor::EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, int offset, const char* text)
bool QueueEditor::EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, const char* args)
{
m_downloadQueue = downloadQueue;
IdList cIdList;
cIdList.push_back(ID);
return InternEditList(nullptr, &cIdList, action, offset, text);
return InternEditList(nullptr, &cIdList, action, args);
}
bool QueueEditor::EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode,
DownloadQueue::EEditAction action, int offset, const char* text)
DownloadQueue::EEditAction action, const char* args)
{
if (action == DownloadQueue::eaPostDelete)
{
return g_PrePostProcessor->EditList(downloadQueue, idList, action, offset, text);
return g_PrePostProcessor->EditList(downloadQueue, idList, action, args);
}
else if (DownloadQueue::eaHistoryDelete <= action && action <= DownloadQueue::eaHistorySetName)
{
return g_HistoryCoordinator->EditList(downloadQueue, idList, action, offset, text);
return g_HistoryCoordinator->EditList(downloadQueue, idList, action, args);
}
m_downloadQueue = downloadQueue;
@@ -376,7 +323,7 @@ bool QueueEditor::EditList(DownloadQueue* downloadQueue, IdList* idList, NameLis
ok = BuildIdListFromNameList(idList, nameList, matchMode, action);
}
ok = ok && (InternEditList(nullptr, idList, action, offset, text) || matchMode == DownloadQueue::mmRegEx);
ok = ok && (InternEditList(nullptr, idList, action, args) || matchMode == DownloadQueue::mmRegEx);
m_downloadQueue->Save();
@@ -384,12 +331,14 @@ bool QueueEditor::EditList(DownloadQueue* downloadQueue, IdList* idList, NameLis
}
bool QueueEditor::InternEditList(ItemList* itemList,
IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
IdList* idList, DownloadQueue::EEditAction action, const char* args)
{
ItemList workItems;
if (!itemList)
{
itemList = &workItems;
int offset = args && (action == DownloadQueue::eaFileMoveOffset ||
action == DownloadQueue::eaGroupMoveOffset) ? atoi(args) : 0;
PrepareList(itemList, idList, action, offset);
}
@@ -404,10 +353,14 @@ bool QueueEditor::InternEditList(ItemList* itemList,
return MergeGroups(itemList);
case DownloadQueue::eaGroupSort:
return SortGroups(itemList, text);
return SortGroups(itemList, args);
case DownloadQueue::eaGroupMoveAfter:
case DownloadQueue::eaGroupMoveBefore:
return MoveGroupsTo(itemList, idList, action == DownloadQueue::eaGroupMoveBefore, args);
case DownloadQueue::eaFileSplit:
return SplitGroup(itemList, text);
return SplitGroup(itemList, args);
case DownloadQueue::eaFileReorder:
ReorderFiles(itemList);
@@ -437,26 +390,26 @@ bool QueueEditor::InternEditList(ItemList* itemList,
break;
case DownloadQueue::eaGroupSetPriority:
SetNzbPriority(item.m_nzbInfo, text);
SetNzbPriority(item.m_nzbInfo, args);
break;
case DownloadQueue::eaGroupSetCategory:
case DownloadQueue::eaGroupApplyCategory:
SetNzbCategory(item.m_nzbInfo, text, action == DownloadQueue::eaGroupApplyCategory);
SetNzbCategory(item.m_nzbInfo, args, action == DownloadQueue::eaGroupApplyCategory);
break;
case DownloadQueue::eaGroupSetName:
SetNzbName(item.m_nzbInfo, text);
SetNzbName(item.m_nzbInfo, args);
break;
case DownloadQueue::eaGroupSetDupeKey:
case DownloadQueue::eaGroupSetDupeScore:
case DownloadQueue::eaGroupSetDupeMode:
SetNzbDupeParam(item.m_nzbInfo, action, text);
SetNzbDupeParam(item.m_nzbInfo, action, args);
break;
case DownloadQueue::eaGroupSetParameter:
SetNzbParameter(item.m_nzbInfo, text);
SetNzbParameter(item.m_nzbInfo, args);
break;
case DownloadQueue::eaGroupMoveTop:
@@ -469,7 +422,7 @@ bool QueueEditor::InternEditList(ItemList* itemList,
case DownloadQueue::eaGroupResume:
case DownloadQueue::eaGroupPauseAllPars:
case DownloadQueue::eaGroupPauseExtraPars:
EditGroup(item.m_nzbInfo, action, offset, text);
EditGroup(item.m_nzbInfo, action, args);
break;
case DownloadQueue::eaGroupDelete:
@@ -482,9 +435,13 @@ bool QueueEditor::InternEditList(ItemList* itemList,
}
else
{
EditGroup(item.m_nzbInfo, action, offset, text);
EditGroup(item.m_nzbInfo, action, args);
}
break;
case DownloadQueue::eaGroupSortFiles:
SortGroupFiles(item.m_nzbInfo);
break;
default:
// suppress compiler warning "enumeration not handled in switch"
@@ -566,15 +523,15 @@ void QueueEditor::PrepareList(ItemList* itemList, IdList* idList,
}
}
}
else if ((offset != 0) &&
(action == DownloadQueue::eaGroupMoveOffset || action == DownloadQueue::eaGroupMoveTop || action == DownloadQueue::eaGroupMoveBottom))
else if (((offset != 0) &&
(action == DownloadQueue::eaGroupMoveOffset || action == DownloadQueue::eaGroupMoveTop || action == DownloadQueue::eaGroupMoveBottom)) ||
action == DownloadQueue::eaGroupMoveBefore || action == DownloadQueue::eaGroupMoveAfter)
{
// add IDs to list in order they currently have in download queue
// per group only one FileInfo is added to the list
int nrEntries = (int)m_downloadQueue->GetQueue()->size();
int lastDestPos = -1;
int start, end, step;
if (offset < 0)
if (offset <= 0)
{
start = 0;
end = nrEntries;
@@ -756,7 +713,7 @@ bool QueueEditor::BuildIdListFromNameList(IdList* idList, NameList* nameList, Do
return true;
}
bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, int offset, const char* text)
bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args)
{
ItemList itemList;
bool allPaused = true;
@@ -803,6 +760,8 @@ bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action,
DownloadQueue::eaFileMoveOffset,
DownloadQueue::eaFileMoveTop,
DownloadQueue::eaFileMoveBottom,
(DownloadQueue::EEditAction)0,
(DownloadQueue::EEditAction)0,
DownloadQueue::eaFilePause,
DownloadQueue::eaFileResume,
DownloadQueue::eaFileDelete,
@@ -816,7 +775,7 @@ bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action,
(DownloadQueue::EEditAction)0,
(DownloadQueue::EEditAction)0 };
bool ok = InternEditList(&itemList, nullptr, GroupToFileMap[action], offset, text);
bool ok = InternEditList(&itemList, nullptr, GroupToFileMap[action], args);
if ((action == DownloadQueue::eaGroupDelete || action == DownloadQueue::eaGroupDupeDelete || action == DownloadQueue::eaGroupFinalDelete) &&
// NZBInfo could have been destroyed already
@@ -950,16 +909,16 @@ void QueueEditor::SetNzbCategory(NzbInfo* nzbInfo, const char* category, bool ap
debug("QueueEditor: setting category '%s' for '%s'", category, nzbInfo->GetName());
bool oldUnpack = g_Options->GetUnpack();
const char* oldPostScript = g_Options->GetPostScript();
const char* oldExtensions = g_Options->GetExtensions();
if (applyParams && !Util::EmptyStr(nzbInfo->GetCategory()))
{
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
if (categoryObj)
{
oldUnpack = categoryObj->GetUnpack();
if (!Util::EmptyStr(categoryObj->GetPostScript()))
if (!Util::EmptyStr(categoryObj->GetExtensions()))
{
oldPostScript = categoryObj->GetPostScript();
oldExtensions = categoryObj->GetExtensions();
}
}
}
@@ -972,16 +931,16 @@ void QueueEditor::SetNzbCategory(NzbInfo* nzbInfo, const char* category, bool ap
}
bool newUnpack = g_Options->GetUnpack();
const char* newPostScript = g_Options->GetPostScript();
const char* newExtensions = g_Options->GetExtensions();
if (!Util::EmptyStr(nzbInfo->GetCategory()))
{
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
if (categoryObj)
{
newUnpack = categoryObj->GetUnpack();
if (!Util::EmptyStr(categoryObj->GetPostScript()))
if (!Util::EmptyStr(categoryObj->GetExtensions()))
{
newPostScript = categoryObj->GetPostScript();
newExtensions = categoryObj->GetExtensions();
}
}
}
@@ -991,15 +950,15 @@ void QueueEditor::SetNzbCategory(NzbInfo* nzbInfo, const char* category, bool ap
nzbInfo->GetParameters()->SetParameter("*Unpack:", newUnpack ? "yes" : "no");
}
if (strcasecmp(oldPostScript, newPostScript))
if (strcasecmp(oldExtensions, newExtensions))
{
// add new params not existed in old category
Tokenizer tokNew(newPostScript, ",;");
Tokenizer tokNew(newExtensions, ",;");
while (const char* newScriptName = tokNew.Next())
{
bool found = false;
const char* oldScriptName;
Tokenizer tokOld(oldPostScript, ",;");
Tokenizer tokOld(oldExtensions, ",;");
while ((oldScriptName = tokOld.Next()) && !found)
{
found = !strcasecmp(newScriptName, oldScriptName);
@@ -1011,12 +970,12 @@ void QueueEditor::SetNzbCategory(NzbInfo* nzbInfo, const char* category, bool ap
}
// remove old params not existed in new category
Tokenizer tokOld(oldPostScript, ",;");
Tokenizer tokOld(oldExtensions, ",;");
while (const char* oldScriptName = tokOld.Next())
{
bool found = false;
const char* newScriptName;
Tokenizer tokNew(newPostScript, ",;");
Tokenizer tokNew(newExtensions, ",;");
while ((newScriptName = tokNew.Next()) && !found)
{
found = !strcasecmp(newScriptName, oldScriptName);
@@ -1084,10 +1043,137 @@ bool QueueEditor::SplitGroup(ItemList* itemList, const char* name)
bool QueueEditor::SortGroups(ItemList* itemList, const char* sort)
{
AlignGroups(itemList);
GroupSorter sorter(m_downloadQueue->GetQueue(), itemList);
return sorter.Execute(sort);
}
void QueueEditor::AlignGroups(ItemList* itemList)
{
NzbList* nzbList = m_downloadQueue->GetQueue();
NzbInfo* lastNzbInfo = nullptr;
uint32 lastNum = 0;
uint32 num = 0;
while (num < nzbList->size())
{
std::unique_ptr<NzbInfo>& nzbInfo = nzbList->at(num);
bool selected = false;
for (QueueEditor::EditItem& item : itemList)
{
if (item.m_nzbInfo == nzbInfo.get())
{
selected = true;
break;
}
}
if (selected)
{
if (lastNzbInfo && num - lastNum > 1)
{
std::unique_ptr<NzbInfo> movedNzbInfo = std::move(*(nzbList->begin() + num));
nzbList->erase(nzbList->begin() + num);
nzbList->insert(nzbList->begin() + lastNum + 1, std::move(movedNzbInfo));
lastNum++;
}
else
{
lastNum = num;
}
lastNzbInfo = nzbInfo.get();
}
num++;
}
}
bool QueueEditor::ItemListContainsItem(ItemList* itemList, int id)
{
return std::find_if(itemList->begin(), itemList->end(),
[id](const EditItem& item)
{
return item.m_nzbInfo->GetId() == id;
}) != itemList->end();
};
bool QueueEditor::MoveGroupsTo(ItemList* itemList, IdList* idList, bool before, const char* args)
{
if (itemList->size() == 0 || Util::EmptyStr(args))
{
return false;
}
int targetId = atoi(args);
int offset = 0;
// check if target is in list of moved items
if (ItemListContainsItem(itemList, targetId))
{
// find the next item to use as target-before
bool found = false;
bool targetSet = false;
for (NzbInfo* nzbInfo : m_downloadQueue->GetQueue())
{
if (found)
{
if (!ItemListContainsItem(itemList, nzbInfo->GetId()))
{
targetId = nzbInfo->GetId();
before = true;
targetSet = true;
break;
}
}
else if (targetId == nzbInfo->GetId())
{
found = true;
}
}
if (!targetSet)
{
// there are no next item; move to the bottom then
offset = MAX_ID;
}
}
AlignGroups(itemList);
if (offset == 0)
{
// calculate offset between first moving item and target
int moveId = itemList->at(0).m_nzbInfo->GetId();
bool progress = false;
int step = 0;
for (NzbInfo* nzbInfo : m_downloadQueue->GetQueue())
{
int id = nzbInfo->GetId();
if (id == targetId || id == moveId)
{
if (!progress)
{
step = id == targetId ? -1 : 1;
offset = (before ? 0 : 1) - (step > 0 ? itemList->size() : 0);
progress = true;
}
else
{
break;
}
}
if (progress)
{
offset += step;
}
}
}
return InternEditList(nullptr, idList, DownloadQueue::eaGroupMoveOffset,
CString::FormatStr("%i", offset));
}
void QueueEditor::ReorderFiles(ItemList* itemList)
{
if (itemList->size() == 0)
@@ -1178,6 +1264,54 @@ void QueueEditor::SetNzbDupeParam(NzbInfo* nzbInfo, DownloadQueue::EEditAction a
}
}
void QueueEditor::SortGroupFiles(NzbInfo* nzbInfo)
{
debug("QueueEditor: sorting inner files for '%s'", nzbInfo->GetName());
std::sort(nzbInfo->GetFileList()->begin(), nzbInfo->GetFileList()->end(),
[](const std::unique_ptr<FileInfo>& fileInfo1, const std::unique_ptr<FileInfo>& fileInfo2)
{
if (!fileInfo1->GetParFile() && !fileInfo2->GetParFile())
{
// ".rar"-files are ordered before ".r01", etc. files
int len1 = strlen(fileInfo1->GetFilename());
int len2 = strlen(fileInfo2->GetFilename());
const char* ext1 = strrchr(fileInfo1->GetFilename(), '.');
const char* ext2 = strrchr(fileInfo2->GetFilename(), '.');
int ext1len = ext1 ? strlen(ext1) : 0;
int ext2len = ext2 ? strlen(ext2) : 0;
bool sameBaseName = len1 == len2 && ext1len == 4 && ext2len == 4 &&
!strncmp(fileInfo1->GetFilename(), fileInfo2->GetFilename(), len1 - 4);
if (sameBaseName && !strcmp(ext1, ".rar") && ext2[1] == 'r' &&
isdigit(ext2[2]) && isdigit(ext2[3]))
{
return true;
}
else if (sameBaseName && !strcmp(ext2, ".rar") && ext1[1] == 'r' &&
isdigit(ext1[2]) && isdigit(ext1[3]))
{
return false;
}
}
else if (fileInfo1->GetParFile() && fileInfo2->GetParFile() &&
ParParser::SameParCollection(fileInfo1->GetFilename(), fileInfo2->GetFilename(),
fileInfo1->GetFilenameConfirmed() && fileInfo2->GetFilenameConfirmed()))
{
return fileInfo1->GetSize() < fileInfo2->GetSize();
}
else if (!fileInfo1->GetParFile() && fileInfo2->GetParFile())
{
return true;
}
else if (fileInfo1->GetParFile() && !fileInfo2->GetParFile())
{
return false;
}
return strcmp(fileInfo1->GetFilename(), fileInfo2->GetFilename()) < 0;
});
}
bool QueueEditor::DeleteUrl(NzbInfo* nzbInfo, DownloadQueue::EEditAction action)
{
return g_UrlCoordinator->DeleteQueueEntry(m_downloadQueue, nzbInfo, action == DownloadQueue::eaGroupFinalDelete);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,8 +26,8 @@
class QueueEditor
{
public:
bool EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, int offset, const char* text);
bool EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action, int offset, const char* text);
bool EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, const char* args);
bool EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action, const char* args);
private:
class EditItem
@@ -46,10 +46,10 @@ private:
DownloadQueue* m_downloadQueue;
FileInfo* FindFileInfo(int id);
bool InternEditList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text);
bool InternEditList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, const char* args);
void PrepareList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, int offset);
bool BuildIdListFromNameList(IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action);
bool EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, int offset, const char* text);
bool EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args);
void PauseParsInGroups(ItemList* itemList, bool extraParsOnly);
void PausePars(RawFileList* fileList, bool extraParsOnly);
void SetNzbPriority(NzbInfo* nzbInfo, const char* priority);
@@ -57,15 +57,19 @@ private:
void SetNzbName(NzbInfo* nzbInfo, const char* name);
bool MergeGroups(ItemList* itemList);
bool SortGroups(ItemList* itemList, const char* sort);
void AlignGroups(ItemList* itemList);
bool MoveGroupsTo(ItemList* itemList, IdList* idList, bool before, const char* args);
bool SplitGroup(ItemList* itemList, const char* name);
bool DeleteUrl(NzbInfo* nzbInfo, DownloadQueue::EEditAction action);
void ReorderFiles(ItemList* itemList);
void SetNzbParameter(NzbInfo* nzbInfo, const char* paramString);
void SetNzbDupeParam(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* text);
void SetNzbDupeParam(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args);
void PauseUnpauseEntry(FileInfo* fileInfo, bool pause);
void DeleteEntry(FileInfo* fileInfo);
void MoveEntry(FileInfo* fileInfo, int offset);
void MoveGroup(NzbInfo* nzbInfo, int offset);
void SortGroupFiles(NzbInfo* nzbInfo);
bool ItemListContainsItem(ItemList* itemList, int id);
friend class GroupSorter;
};

View File

@@ -28,6 +28,8 @@
#include "Util.h"
#include "FileSystem.h"
int Scanner::m_idGen = 0;
Scanner::QueueData::QueueData(const char* filename, const char* nzbName, const char* category,
int priority, const char* dupeKey, int dupeScore, EDupeMode dupeMode,
NzbParameterList* parameters, bool addTop, bool addPaused, NzbInfo* urlInfo,
@@ -72,8 +74,7 @@ void Scanner::QueueData::SetNzbId(int nzbId)
void Scanner::InitOptions()
{
m_nzbDirInterval = g_Options->GetNzbDirInterval() * 1000;
const char* scanScript = g_Options->GetScanScript();
m_scanScript = scanScript && strlen(scanScript) > 0;
m_scanScript = ScanScriptController::HasScripts();
}
void Scanner::ServiceWork()
@@ -96,7 +97,7 @@ void Scanner::ServiceWork()
CheckIncomingNzbs(g_Options->GetNzbDir(), "", checkStat);
if (!checkStat && m_scanScript)
{
// if immediate scan requested, we need second scan to process files extracted by NzbProcess-script
// if immediate scan requested, we need second scan to process files extracted by scan-scripts
CheckIncomingNzbs(g_Options->GetNzbDir(), "", checkStat);
}
m_scanning = false;
@@ -105,7 +106,7 @@ void Scanner::ServiceWork()
// if NzbDirFileAge is less than NzbDirInterval (that can happen if NzbDirInterval
// is set for rare scans like once per hour) we make 4 scans:
// - one additional scan is neccessary to check sizes of detected files;
// - another scan is required to check files which were extracted by NzbProcess-script;
// - another scan is required to check files which were extracted by scan-scripts;
// - third scan is needed to check sizes of extracted files.
if (g_Options->GetNzbDirInterval() > 0 && g_Options->GetNzbDirFileAge() < g_Options->GetNzbDirInterval())
{
@@ -347,7 +348,7 @@ void Scanner::ProcessIncomingFile(const char* directory, const char* baseFilenam
void Scanner::InitPPParameters(const char* category, NzbParameterList* parameters, bool reset)
{
bool unpack = g_Options->GetUnpack();
const char* postScript = g_Options->GetPostScript();
const char* extensions = g_Options->GetExtensions();
if (!Util::EmptyStr(category))
{
@@ -355,9 +356,9 @@ void Scanner::InitPPParameters(const char* category, NzbParameterList* parameter
if (categoryObj)
{
unpack = categoryObj->GetUnpack();
if (!Util::EmptyStr(categoryObj->GetPostScript()))
if (!Util::EmptyStr(categoryObj->GetExtensions()))
{
postScript = categoryObj->GetPostScript();
extensions = categoryObj->GetExtensions();
}
}
}
@@ -372,13 +373,20 @@ void Scanner::InitPPParameters(const char* category, NzbParameterList* parameter
parameters->SetParameter("*Unpack:", unpack ? "yes" : "no");
if (!Util::EmptyStr(postScript))
if (!Util::EmptyStr(extensions))
{
// split szPostScript into tokens and create pp-parameter for each token
Tokenizer tok(postScript, ",;");
// create pp-parameter for each post-processing or queue- script
Tokenizer tok(extensions, ",;");
while (const char* scriptName = tok.Next())
{
parameters->SetParameter(BString<1024>("%s:", scriptName), "yes");
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if ((script.GetPostScript() || script.GetQueueScript()) &&
FileSystem::SameFilename(scriptName, script.GetName()))
{
parameters->SetParameter(BString<1024>("%s:", scriptName), "yes");
}
}
}
}
}
@@ -487,8 +495,8 @@ Scanner::EAddStatus Scanner::AddExternalFile(const char* nzbName, const char* ca
}
else
{
int num = 1;
while (num == 1 || FileSystem::FileExists(tempFileName))
int num = ++m_idGen;
while (tempFileName.Empty() || FileSystem::FileExists(tempFileName))
{
tempFileName.Format("%s%cnzb-%i.tmp", g_Options->GetTempDir(), PATH_SEPARATOR, num);
num++;

View File

@@ -114,6 +114,7 @@ private:
QueueList m_queueList;
bool m_scanning = false;
Mutex m_scanMutex;
static int m_idGen;
void CheckIncomingNzbs(const char* directory, const char* category, bool checkStat);
bool AddFileToQueue(const char* filename, const char* nzbName, const char* category,

View File

@@ -117,21 +117,30 @@ void UrlCoordinator::Run()
}
}
WaitJobs();
debug("Exiting UrlCoordinator-loop");
}
void UrlCoordinator::WaitJobs()
{
// waiting for downloads
debug("UrlCoordinator: waiting for Downloads to complete");
bool completed = false;
while (!completed)
while (true)
{
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
completed = m_activeDownloads.size() == 0;
if (m_activeDownloads.empty())
{
break;
}
}
usleep(100 * 1000);
ResetHangingDownloads();
}
debug("UrlCoordinator: Downloads are completed");
debug("Exiting UrlCoordinator-loop");
debug("UrlCoordinator: Downloads are completed");
}
void UrlCoordinator::Stop()

View File

@@ -56,6 +56,7 @@ private:
void StartUrlDownload(NzbInfo* nzbInfo);
void UrlCompleted(UrlDownloader* urlDownloader);
void ResetHangingDownloads();
void WaitJobs();
};
extern UrlCoordinator* g_UrlCoordinator;

View File

@@ -860,7 +860,9 @@ void EditQueueBinCommand::Execute()
bool ok = DownloadQueue::Guard()->EditList(
nrIdEntries > 0 ? &cIdList : nullptr,
nrNameEntries > 0 ? &cNameList : nullptr,
(DownloadQueue::EMatchMode)matchMode, (DownloadQueue::EEditAction)action, offset, text);
(DownloadQueue::EMatchMode)matchMode, (DownloadQueue::EEditAction)action,
action == DownloadQueue::eaFileMoveOffset || action == DownloadQueue::eaGroupMoveOffset ?
*CString::FormatStr("%i", offset) : text);
if (ok)
{

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@
#ifndef MESSAGEBASE_H
#define MESSAGEBASE_H
static const int32 NZBMESSAGE_SIGNATURE = 0x6E7A6228; // = "nzb-XX" (protocol version)
static const int32 NZBMESSAGE_SIGNATURE = 0x6E7A6231; // = "nzb-XX" (protocol version)
static const int NZBREQUESTFILENAMESIZE = 512;
static const int NZBREQUESTPASSWORDSIZE = 32;

View File

@@ -957,7 +957,9 @@ bool RemoteClient::RequestPostQueue()
completed.Format(", %i%s", (int)(stageProgress / 10), "%");
}
const char* postStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Unpacking", ", Executing postprocess-script", "" };
const char* postStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing",
", Verifying repaired files", ", Par-Renaming", ", Rar-Renaming", ", Unpacking", ", Cleaning up",
", Moving", ", Executing postprocess-script", "" };
char* infoName = bufPtr + sizeof(SNzbPostQueueResponseEntry) + ntohl(postQueueAnswer->m_nzbFilenameLen);
printf("[%i] %s%s%s\n", ntohl(postQueueAnswer->m_id), infoName, postStageName[ntohl(postQueueAnswer->m_stage)], *completed);

View File

@@ -90,7 +90,10 @@ void WebProcessor::Execute()
ParseUrl();
if (!CheckCredentials())
m_rpcRequest = XmlRpcProcessor::IsRpcRequest(m_url);
m_authorized = CheckCredentials();
if ((!g_Options->GetFormAuth() || m_rpcRequest) && !m_authorized)
{
SendAuthResponse();
return;
@@ -124,11 +127,12 @@ void WebProcessor::ParseHeaders()
{
if (char* pe = strrchr(p, '\r')) *pe = '\0';
debug("header=%s", p);
if (!strncasecmp(p, "Content-Length: ", 16))
{
m_contentLen = atoi(p + 16);
}
if (!strncasecmp(p, "Authorization: Basic ", 21))
else if (!strncasecmp(p, "Authorization: Basic ", 21) && Util::EmptyStr(m_authInfo))
{
char* authInfo64 = p + 21;
if (strlen(authInfo64) > sizeof(m_authInfo))
@@ -138,20 +142,39 @@ void WebProcessor::ParseHeaders()
}
m_authInfo[WebUtil::DecodeBase64(authInfo64, 0, m_authInfo)] = '\0';
}
if (!strncasecmp(p, "Accept-Encoding: ", 17))
else if (!strncasecmp(p, "X-Authorization: Basic ", 23))
{
char* authInfo64 = p + 23;
if (strlen(authInfo64) > sizeof(m_authInfo))
{
error("Invalid-request: auth-info too big");
return;
}
m_authInfo[WebUtil::DecodeBase64(authInfo64, 0, m_authInfo)] = '\0';
}
else if (!strncasecmp(p, "Accept-Encoding: ", 17))
{
m_gzip = strstr(p, "gzip");
}
if (!strncasecmp(p, "Origin: ", 8))
else if (!strncasecmp(p, "Origin: ", 8))
{
m_origin = p + 8;
}
if (!strncasecmp(p, "X-Auth-Token: ", 14))
else if (!strncasecmp(p, "Cookie: ", 8))
{
strncpy(m_authToken, p + 14, sizeof(m_authToken)-1);
m_authToken[sizeof(m_authToken)-1] = '\0';
debug("%s", p);
const char* tok = strstr(p, "Auth-Token=");
if (tok && tok[11] != ';' && tok[11] != '\0')
{
strncpy(m_authToken, tok + 11, sizeof(m_authToken)-1);
m_authToken[sizeof(m_authToken)-1] = '\0';
}
}
if (*p == '\0')
else if (!strncasecmp(p, "X-Forwarded-For: ", 17))
{
m_forwardedFor = p + 17;
}
else if (*p == '\0')
{
break;
}
@@ -159,7 +182,7 @@ void WebProcessor::ParseHeaders()
debug("URL=%s", *m_url);
debug("Authorization=%s", m_authInfo);
debug("X-Auth-Token=%s", m_authToken);
debug("Auth-Token=%s", m_authToken);
}
void WebProcessor::ParseUrl()
@@ -245,8 +268,10 @@ bool WebProcessor::CheckCredentials()
}
else
{
warn("Request received on port %i from %s, but username or password invalid (%s:%s)",
g_Options->GetControlPort(), m_connection->GetRemoteAddr(), m_authInfo, pw);
warn("Request received on port %i from %s%s, but username or password invalid (%s:%s)",
g_Options->GetControlPort(), m_connection->GetRemoteAddr(),
!m_forwardedFor.Empty() ? (char*)BString<1024>(" (forwarded for: %s)", *m_forwardedFor) : "",
m_authInfo, pw);
return false;
}
}
@@ -281,7 +306,7 @@ void WebProcessor::Dispatch()
return;
}
if (XmlRpcProcessor::IsRpcRequest(m_url))
if (m_rpcRequest)
{
XmlRpcProcessor processor;
processor.SetRequest(m_request);
@@ -333,12 +358,15 @@ void WebProcessor::SendAuthResponse()
{
const char* AUTH_RESPONSE_HEADER =
"HTTP/1.0 401 Unauthorized\r\n"
"WWW-Authenticate: Basic realm=\"NZBGet\"\r\n"
"%s"
"Connection: close\r\n"
"Content-Type: text/plain\r\n"
"Server: nzbget-%s\r\n"
"\r\n";
BString<1024> responseHeader(AUTH_RESPONSE_HEADER, Util::VersionRevision());
BString<1024> responseHeader(AUTH_RESPONSE_HEADER,
g_Options->GetFormAuth() ? "" : "WWW-Authenticate: Basic realm=\"NZBGet\"\r\n",
Util::VersionRevision());
// Send the response answer
debug("ResponseHeader=%s", *responseHeader);
@@ -417,7 +445,8 @@ void WebProcessor::SendBodyResponse(const char* body, int bodyLen, const char* c
"Access-Control-Allow-Credentials: true\r\n"
"Access-Control-Max-Age: 86400\r\n"
"Access-Control-Allow-Headers: Content-Type, Authorization\r\n"
"X-Auth-Token: %s\r\n"
"Set-Cookie: Auth-Type=%s\r\n"
"Set-Cookie: Auth-Token=%s\r\n"
"Content-Length: %i\r\n"
"%s" // Content-Type: xxx
"%s" // Content-Encoding: gzip
@@ -454,7 +483,9 @@ void WebProcessor::SendBodyResponse(const char* body, int bodyLen, const char* c
BString<1024> responseHeader(RESPONSE_HEADER,
m_origin.Str(),
m_serverAuthToken[m_userAccess], bodyLen, *contentTypeHeader,
g_Options->GetFormAuth() ? "form" : "http",
m_authorized ? m_serverAuthToken[m_userAccess] : "",
bodyLen, *contentTypeHeader,
gzip ? "Content-Encoding: gzip\r\n" : "",
Util::VersionRevision());

Some files were not shown because too many files have changed in this diff Show More