Compare commits

...

141 Commits
v18.1 ... v19.1

Author SHA1 Message Date
Andrey Prygunkov
c0eedc342b updated version string to "19.1" 2017-07-08 18:20:02 +02:00
Andrey Prygunkov
eb4b8b30e1 updated ChangeLog for v19.1 2017-07-08 18:13:06 +02:00
Andrey Prygunkov
593e29f163 updated version string to "19.1-testing" 2017-07-02 18:08:17 +02:00
Andrey Prygunkov
c4d29bc57f #408: abort direct unpack if destination path was changed
For example caused by a change of category during direct unpack.
2017-07-02 17:26:35 +02:00
Andrey Prygunkov
92db424ce0 #405: safety check for presence of unpacked files
Before trying to reuse them in default unpack module.
2017-07-01 22:32:11 +02:00
Andrey Prygunkov
17fbb795c8 #407: fixed: rar-rename may fail to read encrypted rar3-archives 2017-07-01 21:59:19 +02:00
Andrey Prygunkov
35e65e792b #406: fixed incorrect renaming in rar-renamer
Improved detection of rar-sets.
2017-06-30 19:09:34 +02:00
Andrey Prygunkov
486b9d7d2b #405: discard info about extracted archives
if found archive files not processed by direct unpack; to prevent
reusing on next unpack attempt
2017-06-30 17:36:19 +02:00
Andrey Prygunkov
3abaa0fb3f updated version string to "20.0-testing" 2017-06-29 21:53:50 +02:00
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
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
101 changed files with 5405 additions and 972 deletions

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

110
ChangeLog
View File

@@ -1,3 +1,113 @@
nzbget-19.1:
- proper handling of changing category (and destination path) during direct
unpack; direct unpack now gracefully aborts with cleanup; the files will
be unpacked during post-processing stage;
- fixed: password protected downloads of certain kind may sometimes end up
with no files if direct unpack was active;
- fixed: rar-renamer mistakenly renamed some encrypted rar3 files causing
unnecessary repair;
- fixed: rar-renamer could not process some encrypted rar3-archives.
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;

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 \
@@ -106,6 +108,10 @@ nzbget_SOURCES = \
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 \
@@ -235,6 +241,7 @@ nzbget_SOURCES += \
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 \
@@ -455,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 */" ;\

128
Makefile.in vendored
View File

@@ -113,6 +113,7 @@ bin_PROGRAMS = nzbget$(EXEEXT)
@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 \
@@ -153,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 \
@@ -205,9 +209,13 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.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/queue/DiskState.cpp \
daemon/queue/DiskState.h daemon/queue/DownloadInfo.cpp \
daemon/queue/DownloadInfo.h daemon/queue/DupeCoordinator.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 \
daemon/queue/DupeCoordinator.h \
daemon/queue/HistoryCoordinator.cpp \
daemon/queue/HistoryCoordinator.h daemon/queue/NzbFile.cpp \
@@ -261,6 +269,7 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.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/postprocess/ParCheckerTest.cpp \
@@ -284,7 +293,9 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \
@WITH_TESTS_TRUE@ FeedFilterTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ DupeMatcherTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ RarRenamerTest.$(OBJEXT) \
@WITH_TESTS_TRUE@ RarReaderTest.$(OBJEXT) NzbFileTest.$(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)
@@ -293,7 +304,8 @@ am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \
@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) \
@@ -309,11 +321,12 @@ am_nzbget_OBJECTS = Connection.$(OBJEXT) TlsSocket.$(OBJEXT) \
ParChecker.$(OBJEXT) ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \
PrePostProcessor.$(OBJEXT) RarRenamer.$(OBJEXT) \
RarReader.$(OBJEXT) Rename.$(OBJEXT) Repair.$(OBJEXT) \
Unpack.$(OBJEXT) DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
DupeCoordinator.$(OBJEXT) HistoryCoordinator.$(OBJEXT) \
NzbFile.$(OBJEXT) QueueCoordinator.$(OBJEXT) \
QueueEditor.$(OBJEXT) Scanner.$(OBJEXT) \
UrlCoordinator.$(OBJEXT) BinRpc.$(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) \
@@ -480,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 \
@@ -532,9 +548,13 @@ nzbget_SOURCES = daemon/connect/Connection.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/queue/DiskState.cpp \
daemon/queue/DiskState.h daemon/queue/DownloadInfo.cpp \
daemon/queue/DownloadInfo.h daemon/queue/DupeCoordinator.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 \
daemon/queue/DupeCoordinator.h \
daemon/queue/HistoryCoordinator.cpp \
daemon/queue/HistoryCoordinator.h daemon/queue/NzbFile.cpp \
@@ -858,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@
@@ -1027,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
@@ -1573,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
@@ -2329,6 +2395,20 @@ RarReaderTest.obj: tests/postprocess/RarReaderTest.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 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
@@ -2810,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

@@ -134,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

107
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 18.1.
# Generated by GNU Autoconf 2.61 for nzbget 19.1.
#
# 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='18.1'
PACKAGE_STRING='nzbget 18.1'
PACKAGE_VERSION='19.1'
PACKAGE_STRING='nzbget 19.1'
PACKAGE_BUGREPORT='hugbug@users.sourceforge.net'
ac_unique_file="daemon/main/nzbget.cpp"
@@ -1255,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 18.1 to adapt to many kinds of systems.
\`configure' configures nzbget 19.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1326,7 +1326,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of nzbget 18.1:";;
short | recursive ) echo "Configuration of nzbget 19.1:";;
esac
cat <<\_ACEOF
@@ -1477,7 +1477,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
nzbget configure 18.1
nzbget configure 19.1
generated by GNU Autoconf 2.61
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1491,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 18.1, which was
It was created by nzbget $as_me 19.1, which was
generated by GNU Autoconf 2.61. Invocation command line was
$ $0 $@
@@ -2287,7 +2287,7 @@ fi
# Define the identity of the package.
PACKAGE='nzbget'
VERSION='18.1'
VERSION='19.1'
cat >>confdefs.h <<_ACEOF
@@ -9989,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
@@ -12210,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 18.1, which was
This file was extended by nzbget $as_me 19.1, which was
generated by GNU Autoconf 2.61. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -12263,7 +12350,7 @@ Report bugs to <bug-autoconf@gnu.org>."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
nzbget config.status 18.1
nzbget config.status 19.1
configured by $0, generated by GNU Autoconf 2.61,
with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"

View File

@@ -21,7 +21,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(nzbget, 18.1, hugbug@users.sourceforge.net)
AC_INIT(nzbget, 19.1, hugbug@users.sourceforge.net)
AC_CONFIG_AUX_DIR(posix)
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign])
@@ -396,6 +396,8 @@ if test "$USETLS" = "yes"; then
if test "$FOUND" = "yes"; then
TLSLIB="OpenSSL"
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

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
@@ -202,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);
}
@@ -217,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);
}
@@ -238,6 +239,8 @@ void TlsSocket::ReportError(const char* errMsg)
{
PrintError(errMsg);
}
errcode = ERR_get_error();
} while (errcode);
#endif /* HAVE_OPENSSL */
}
@@ -254,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;
}
@@ -266,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;
}
@@ -276,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;
}
@@ -291,7 +294,7 @@ bool TlsSocket::Start()
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;
}
@@ -301,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;
}
@@ -311,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;
}
@@ -321,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;
}
@@ -335,7 +344,7 @@ bool TlsSocket::Start()
if (!m_context)
{
ReportError("Could not create TLS context");
ReportError("Could not create TLS context", false);
return false;
}
@@ -343,35 +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");
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;
}
@@ -393,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;
}
@@ -403,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

@@ -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

@@ -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" };

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

@@ -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";
@@ -90,11 +93,13 @@ 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_DIRECTRENAME = "DirectRename";
static const char* OPTION_UMASK = "UMask";
static const char* OPTION_UPDATEINTERVAL = "UpdateInterval";
static const char* OPTION_CURSESNZBNAME = "CursesNzbName";
@@ -106,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";
@@ -114,6 +120,7 @@ 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";
@@ -134,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";
@@ -193,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) ||
@@ -422,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");
@@ -453,11 +467,13 @@ void Options::InitDefaults()
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_EXTENSIONS, "");
SetOption(OPTION_DAEMONUSERNAME, "root");
@@ -472,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");
@@ -480,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");
@@ -503,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()
@@ -659,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);
@@ -719,6 +739,7 @@ void Options::InitOptions()
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);
@@ -726,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 };
@@ -757,6 +783,11 @@ void Options::InitOptions()
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;
@@ -979,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));
@@ -999,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,
@@ -1495,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;
}
@@ -1682,6 +1725,11 @@ void Options::ConvertOldOption(CString& option, CString& value)
{
option = "BrokenLog";
}
if (!strcasecmp(option, "DumpCore"))
{
option = OPTION_CRASHDUMP;
}
}
void Options::CheckOptions()
@@ -1697,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
@@ -1713,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;

View File

@@ -92,6 +92,12 @@ public:
ppAggressive,
ppRocket
};
enum EFileNaming
{
nfAuto,
nfArticle,
nfNzb
};
class OptEntry
{
@@ -161,7 +167,7 @@ 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,
@@ -225,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; }
@@ -263,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; }
@@ -271,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; }
@@ -289,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); }
@@ -369,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;
@@ -393,6 +410,7 @@ private:
int m_parBuffer = 0;
int m_parThreads = 0;
bool m_rarRename = false;
bool m_directRename = false;
EHealthCheck m_healthCheck = hcNone;
CString m_extensions;
CString m_scriptOrder;
@@ -408,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;
@@ -416,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;
@@ -434,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;

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;
}
}
}

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"
@@ -85,6 +85,7 @@ ArticleCache* g_ArticleCache;
QueueScriptCoordinator* g_QueueScriptCoordinator;
ServiceCoordinator* g_ServiceCoordinator;
ScriptConfig* g_ScriptConfig;
CommandScriptLog* g_CommandScriptLog;
#ifdef WIN32
WinConsole* g_WinConsole;
#endif
@@ -174,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,
@@ -205,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
@@ -280,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())
{
@@ -365,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>();
@@ -382,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");
@@ -437,6 +445,7 @@ void NZBGet::Cleanup()
g_QueueScriptCoordinator = nullptr;
g_Maintenance = nullptr;
g_StatMeter = nullptr;
g_CommandScriptLog = nullptr;
#ifdef WIN32
g_WinConsole = nullptr;
#endif
@@ -694,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()
@@ -938,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__
@@ -205,6 +208,7 @@ 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
@@ -222,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

View File

@@ -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,558 @@
/*
* 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);
// Stop direct unpack if destination directory was changed during unpack
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_nzbId);
if (nzbInfo && (strcmp(m_destDir, nzbInfo->GetDestDir()) ||
strcmp(m_finalDir, nzbInfo->BuildFinalDirName())))
{
nzbInfo->AddMessage(Message::mkWarning, BString<1024>("Destination directory changed for %s", nzbInfo->GetName()));
Stop(downloadQueue, nzbInfo);
}
}
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

@@ -408,14 +408,14 @@ ParChecker::EStatus ParChecker::RunParCheckAll()
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, 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);
@@ -1192,7 +1192,7 @@ void ParChecker::Cancel()
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))
{

View File

@@ -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,11 +59,11 @@ 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, false, &baseLen1, nullptr) &&
ParseParFilename(filename2, false, &baseLen2, nullptr) &&
return ParseParFilename(filename1, confirmedFilenames, &baseLen1, nullptr) &&
ParseParFilename(filename2, confirmedFilenames, &baseLen2, nullptr) &&
baseLen1 == baseLen2 &&
!strncasecmp(filename1, filename2, baseLen1);
}

View File

@@ -31,7 +31,7 @@ public:
static bool FindMainPars(const char* path, ParFileList* fileList);
static bool ParseParFilename(const char* parFilename, bool confirmedFilename, int* baseNameLen, int* blocks);
static bool SameParCollection(const char* filename1, const char* filename2);
static bool SameParCollection(const char* filename1, const char* filename2, bool confirmedFilenames);
};
#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
@@ -34,6 +34,7 @@
#include "NzbFile.h"
#include "QueueScript.h"
#include "ParParser.h"
#include "DirectUnpack.h"
PrePostProcessor::PrePostProcessor()
{
@@ -76,7 +77,7 @@ void PrePostProcessor::WaitJobs()
{
debug("PrePostProcessor: waiting for jobs to complete");
// wait 5 seconds until all jobs gracefully finish
// wait 5 seconds until all post-processing jobs gracefully finish
time_t waitStart = Util::CurrentTime();
while (Util::CurrentTime() < waitStart + 5)
{
@@ -91,7 +92,7 @@ void PrePostProcessor::WaitJobs()
usleep(200 * 1000);
}
// kill remaining jobs; not safe but we can't wait any longer
// kill remaining post-processing jobs; not safe but we can't wait any longer
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
for (NzbInfo* postJob : m_activeJobs)
@@ -107,13 +108,41 @@ void PrePostProcessor::WaitJobs()
}
}
// 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();
for (NzbInfo* postJob : m_activeJobs)
{
@@ -122,6 +151,14 @@ void PrePostProcessor::Stop()
postJob->GetPostInfo()->GetPostThread()->Stop();
}
}
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
if (nzbInfo->GetUnpackThread())
{
((DirectUnpack*)nzbInfo->GetUnpackThread())->Stop(downloadQueue, nzbInfo);
}
}
}
/**
@@ -168,6 +205,10 @@ void PrePostProcessor::DownloadQueueUpdate(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() &&
@@ -182,9 +223,9 @@ void PrePostProcessor::DownloadQueueUpdate(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
@@ -206,7 +247,7 @@ void PrePostProcessor::DownloadQueueUpdate(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());
@@ -217,7 +258,7 @@ void PrePostProcessor::DownloadQueueUpdate(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());
@@ -238,8 +279,13 @@ void PrePostProcessor::NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
{
if (g_Options->GetParCheck() != Options::pcForce)
{
downloadQueue->EditEntry(nzbInfo->GetId(),
DownloadQueue::eaGroupPauseExtraPars, nullptr);
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 ||
@@ -277,16 +323,33 @@ void PrePostProcessor::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbI
nzbInfo->SetParStatus(NzbInfo::psSkipped);
}
if (nzbInfo->GetUnpackThread())
{
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);
@@ -355,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);
}
}
@@ -377,10 +440,7 @@ void PrePostProcessor::DeleteCleanup(NzbInfo* nzbInfo)
}
// delete old directory (if empty)
if (FileSystem::DirEmpty(nzbInfo->GetDestDir()))
{
FileSystem::RemoveDirectory(nzbInfo->GetDestDir());
}
FileSystem::DeleteDirectory(nzbInfo->GetDestDir());
}
}
@@ -433,7 +493,8 @@ void PrePostProcessor::CleanupJobs(DownloadQueue* downloadQueue)
[processor = this, downloadQueue](NzbInfo* postJob)
{
PostInfo* postInfo = postJob->GetPostInfo();
if (!postInfo->GetWorking())
if (!postInfo->GetWorking() &&
!(postInfo->GetPostThread() && postInfo->GetPostThread()->IsRunning()))
{
delete postInfo->GetPostThread();
postInfo->SetPostThread(nullptr);
@@ -511,11 +572,12 @@ NzbInfo* PrePostProcessor::PickNextJob(DownloadQueue* downloadQueue, bool allowP
{
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()) &&
(allowPar || !nzbInfo1->GetPostInfo()->GetNeedParCheck()) &&
(std::find(m_activeJobs.begin(), m_activeJobs.end(), nzbInfo1) == m_activeJobs.end()) &&
IsNzbFileCompleted(nzbInfo1, true))
nzbInfo1->IsDownloadCompleted(true))
{
nzbInfo = nzbInfo1;
}
@@ -731,7 +793,7 @@ void PrePostProcessor::JobCompleted(DownloadQueue* downloadQueue, PostInfo* post
nzbInfo->LeavePostProcess();
if (IsNzbFileCompleted(nzbInfo, true))
if (nzbInfo->IsDownloadCompleted(true))
{
NzbCompleted(downloadQueue, nzbInfo, false);
}
@@ -739,25 +801,6 @@ void PrePostProcessor::JobCompleted(DownloadQueue* downloadQueue, PostInfo* post
m_queuedJobs--;
}
bool PrePostProcessor::IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars)
{
if (nzbInfo->GetActiveDownloads())
{
return false;
}
for (FileInfo* fileInfo : nzbInfo->GetFileList())
{
if ((!fileInfo->GetPaused() || !ignorePausedPars || !fileInfo->GetParFile()) &&
!fileInfo->GetDeleted())
{
return false;
}
}
return true;
}
void PrePostProcessor::UpdatePauseState()
{
bool needPause = false;
@@ -838,6 +881,11 @@ bool PrePostProcessor::PostQueueDelete(DownloadQueue* downloadQueue, IdList* idL
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");
@@ -847,6 +895,7 @@ 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(),
@@ -870,3 +919,32 @@ bool PrePostProcessor::PostQueueDelete(DownloadQueue* downloadQueue, IdList* idL
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

@@ -61,8 +61,8 @@ private:
bool PostQueueDelete(DownloadQueue* downloadQueue, IdList* idList);
void DownloadQueueUpdate(void* aspect);
void DeleteCleanup(NzbInfo* nzbInfo);
bool IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars);
void WaitJobs();
void FileDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, FileInfo* fileInfo);
};
extern PrePostProcessor* g_PrePostProcessor;

View File

@@ -268,6 +268,12 @@ RarVolume::RarBlock RarVolume::ReadRar3Block(DiskFile& file)
uint16 size = ((uint16)buf[6] << 8) + buf[5];
uint32 blocksize = size;
if (m_encrypted)
{
// Align to 16 bytes
blocksize = (blocksize + ((~blocksize + 1) & (16 - 1)));
}
block.trailsize = blocksize - sizeof(buf);
uint8 addbuf[4];

View File

@@ -224,10 +224,10 @@ void RarRenamer::MakeSets()
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())))
((volume.GetFiles()->front().GetSplitBefore() &&
lastVolume->GetFiles()->back().GetSplitAfter() &&
!strcmp(volume.GetFiles()->front().GetFilename(), lastVolume->GetFiles()->back().GetFilename())) ||
(!volume.GetFiles()->front().GetSplitBefore() && !lastVolume->GetFiles()->back().GetSplitAfter())))
{
debug(" adding %s", FileSystem::BaseFileName(volume.GetFilename()));
set.push_back(&volume);
@@ -237,7 +237,9 @@ void RarRenamer::MakeSets()
}
}
bool completed = !set.back()->GetHasNextVolume();
RarVolume* lastVolume = set.back();
bool completed = !lastVolume->GetHasNextVolume() &&
(lastVolume->GetFiles()->empty() || !lastVolume->GetFiles()->back().GetSplitAfter());
return !completed;
}),

View File

@@ -207,13 +207,13 @@ void RenameController::UpdateRarRenameProgress()
/**
* Update file name in the CompletedFiles-list of NZBInfo
*/
void RenameController::RegisterRenamedFile(const char* oldFilename, const char* newFileName)
void RenameController::RegisterRenamedFile(const char* oldFilename, const char* newFilename)
{
for (CompletedFile& completedFile : m_postInfo->GetNzbInfo()->GetCompletedFiles())
{
if (!strcasecmp(completedFile.GetFileName(), oldFilename))
if (!strcasecmp(completedFile.GetFilename(), oldFilename))
{
completedFile.SetFileName(newFileName);
completedFile.SetFilename(newFilename);
break;
}
}

View File

@@ -78,8 +78,8 @@ private:
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 void RegisterRenamedFile(const char* oldFilename, const char* newFilename)
{ m_owner->RegisterRenamedFile(oldFilename, newFilename); }
virtual bool IsStopped() { return m_owner->IsStopped(); };
private:
RenameController* m_owner;
@@ -92,7 +92,7 @@ private:
void ExecRename(const char* destDir, const char* finalDir, const char* nzbName);
void RenameCompleted();
void RegisterRenamedFile(const char* oldFilename, const char* newFileName);
void RegisterRenamedFile(const char* oldFilename, const char* newFilename);
};
#endif

View File

@@ -74,7 +74,7 @@ ParChecker::EFileStatus RepairController::PostParChecker::FindFileCrc(const char
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 RepairController::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();
@@ -246,7 +246,7 @@ bool RepairController::AddPar(FileInfo* fileInfo, bool deleted)
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
@@ -320,7 +320,7 @@ bool RepairController::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename
}
std::sort(availableBlocks.begin(), availableBlocks.end(),
[](BlockInfo& block1, BlockInfo& block2)
[](const BlockInfo& block1, const BlockInfo& block2)
{
return block1.m_blockCount < block2.m_blockCount;
});
@@ -355,7 +355,7 @@ bool RepairController::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename
{
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);
}
@@ -410,7 +410,8 @@ void RepairController::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo,
if (exactParName)
{
useFile = ParParser::SameParCollection(fileInfo->GetFilename(), FileSystem::BaseFileName(parFilename));
useFile = ParParser::SameParCollection(fileInfo->GetFilename(),
FileSystem::BaseFileName(parFilename), fileInfo->GetFilenameConfirmed());
}
else if (strictParName)
{

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;
{
@@ -103,7 +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->GetNzbInfo()->SetUnpackStatus((NzbInfo::EPostUnpackStatus)m_postInfo->GetLastUnpackStatus());
}
else if (unpack && hasFiles)
{
@@ -113,7 +103,29 @@ void UnpackController::Run()
if (m_hasRarFiles)
{
UnpackArchives(upUnrar, false);
if (m_hasNotUnpackedRarFiles || m_unpackDirCreated)
{
if (m_postInfo->GetNzbInfo()->GetDirectUnpackStatus() == NzbInfo::nsSuccess)
{
if (m_unpackDirCreated)
{
PrintMessage(Message::mkWarning, "Could not find files unpacked by direct unpack, unpacking all files again");
}
else
{
PrintMessage(Message::mkInfo, "Found archive files not processed by direct unpack, unpacking all files again");
}
}
// Discard info about extracted archives to prevent reusing on next unpack attempt
m_postInfo->GetExtractedArchives()->clear();
UnpackArchives(upUnrar, false);
}
else
{
PrintMessage(Message::mkInfo, "Using directly unpacked files");
}
}
if (m_hasSevenZipFiles && m_unpackOk)
@@ -277,7 +289,8 @@ void UnpackController::ExecuteUnrar(const char* password)
}
params.emplace_back("*.rar");
params.push_back(FileSystem::MakeExtendedPath(BString<1024>("%s%c", *m_unpackDir, PATH_SEPARATOR), true));
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();
@@ -604,6 +617,7 @@ void UnpackController::CreateUnpackDir()
const char* destDir = !m_finalDir.Empty() ? *m_finalDir : *m_destDir;
m_unpackDir.Format("%s%c%s", destDir, PATH_SEPARATOR, "_unpack");
m_unpackDirCreated = !FileSystem::DirectoryExists(m_unpackDir);
detail("Unpacking into %s", *m_unpackDir);
@@ -616,12 +630,6 @@ void UnpackController::CreateUnpackDir()
void UnpackController::CheckArchiveFiles()
{
m_hasRarFiles = false;
m_hasRenamedArchiveFiles = false;
m_hasSevenZipFiles = false;
m_hasSevenZipMultiFiles = false;
m_hasSplittedFiles = false;
RegEx regExRar(".*\\.rar$");
RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
RegEx regExSevenZip(".*\\.7z$");
@@ -641,6 +649,10 @@ void UnpackController::CheckArchiveFiles()
if (regExRar.Match(filename))
{
m_hasRarFiles = true;
m_hasNotUnpackedRarFiles |= std::find(
m_postInfo->GetExtractedArchives()->begin(),
m_postInfo->GetExtractedArchives()->end(),
filename) == m_postInfo->GetExtractedArchives()->end();
}
else if (regExSevenZip.Match(filename))
{
@@ -731,7 +743,7 @@ bool UnpackController::Cleanup()
if (!m_unpackOk && m_finalDirCreated)
{
FileSystem::RemoveDirectory(m_finalDir);
FileSystem::DeleteDirectory(m_finalDir);
}
if (m_unpackOk && ok && g_Options->GetUnpackCleanupDisk())
@@ -839,9 +851,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);

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,30 @@ 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_hasRenamedArchiveFiles;
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_hasNotUnpackedRarFiles = 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_unpackDirCreated = 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);

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", 59, 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", 59, 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", 59, 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", 59, 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,%i", (int)nzbInfo->GetParStatus(), (int)nzbInfo->GetUnpackStatus(),
(int)nzbInfo->GetMoveStatus(), (int)nzbInfo->GetParRenameStatus(), (int)nzbInfo->GetRarRenameStatus(),
(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());
@@ -568,17 +572,33 @@ bool DiskState::LoadNzbInfo(NzbInfo* nzbInfo, Servers* servers, StateDiskFile& i
}
nzbInfo->SetFeedId(feedId);
int parStatus, unpackStatus, moveStatus, parRenameStatus, rarRenameStatus, deleteStatus, markStatus, urlStatus;
if (formatVersion < 58 && infile.ScanLine("%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
&parRenameStatus, &deleteStatus, &markStatus, &urlStatus) != 7) goto error;
rarRenameStatus = 0;
if (formatVersion >= 58 && infile.ScanLine("%i,%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
&parRenameStatus, &rarRenameStatus, &deleteStatus, &markStatus, &urlStatus) != 8) 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->SetParRenameStatus((NzbInfo::ERenameStatus)parRenameStatus);
nzbInfo->SetRarRenameStatus((NzbInfo::ERenameStatus)rarRenameStatus);
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 ||
@@ -690,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, ',');
@@ -711,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;
@@ -784,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));
}
@@ -840,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)
@@ -856,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);
@@ -894,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)
@@ -915,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;
@@ -979,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)
@@ -1001,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);
@@ -1020,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)
@@ -1046,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;
@@ -1091,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)
@@ -1330,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,7 +348,15 @@ enum EDupeMode
class NzbInfo
{
public:
enum ERenameStatus
enum EDirectRenameStatus
{
tsNone,
tsRunning,
tsFailure,
tsSuccess
};
enum EPostRenameStatus
{
rsNone,
rsSkipped,
@@ -350,7 +374,15 @@ public:
psManual
};
enum EUnpackStatus
enum EDirectUnpackStatus
{
nsNone,
nsRunning,
nsFailure,
nsSuccess
};
enum EPostUnpackStatus
{
usNone,
usSkipped,
@@ -485,14 +517,18 @@ public:
void BuildDestDirName();
CString BuildFinalDirName();
CompletedFileList* GetCompletedFiles() { return &m_completedFiles; }
ERenameStatus GetParRenameStatus() { return m_parRenameStatus; }
void SetParRenameStatus(ERenameStatus renameStatus) { m_parRenameStatus = renameStatus; }
ERenameStatus GetRarRenameStatus() { return m_rarRenameStatus; }
void SetRarRenameStatus(ERenameStatus renameStatus) { m_rarRenameStatus = 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; }
@@ -556,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; }
@@ -577,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;
@@ -618,10 +665,12 @@ private:
time_t m_maxTime = 0;
int m_priority = 0;
CompletedFileList m_completedFiles;
ERenameStatus m_parRenameStatus = rsNone;
ERenameStatus m_rarRenameStatus = 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;
@@ -655,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;
@@ -698,6 +753,7 @@ public:
};
typedef std::vector<CString> ParredFiles;
typedef std::vector<CString> ExtractedArchives;
NzbInfo* GetNzbInfo() { return m_nzbInfo; }
void SetNzbInfo(NzbInfo* nzbInfo) { m_nzbInfo = nzbInfo; }
@@ -736,6 +792,7 @@ public:
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;
@@ -757,6 +814,7 @@ private:
time_t m_stageTime = 0;
Thread* m_postThread = nullptr;
ParredFiles m_parredFiles;
ExtractedArchives m_extractedArchives;
};
typedef std::vector<int> IdList;
@@ -850,6 +908,7 @@ public:
eaNzbFound,
eaNzbAdded,
eaNzbDeleted,
eaNzbNamed,
eaFileCompleted,
eaFileDeleted,
eaUrlCompleted
@@ -872,7 +931,7 @@ 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 offset relative to the current position in download-queue
@@ -898,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

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());
}
@@ -379,6 +384,7 @@ void HistoryCoordinator::MoveToQueue(DownloadQueue* downloadQueue, HistoryList::
if (!nzbInfo->GetUnpackCleanedUpDisk())
{
nzbInfo->SetUnpackStatus(NzbInfo::usNone);
nzbInfo->SetDirectUnpackStatus(NzbInfo::nsNone);
nzbInfo->SetCleanupStatus(NzbInfo::csNone);
nzbInfo->SetParRenameStatus(NzbInfo::rsNone);
nzbInfo->SetRarRenameStatus(NzbInfo::rsNone);
@@ -493,6 +499,8 @@ void HistoryCoordinator::HistoryRedownload(DownloadQueue* downloadQueue, History
nzbInfo->SetParStatus(NzbInfo::psNone);
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);
@@ -500,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();
@@ -587,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());

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
@@ -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();
@@ -257,7 +261,7 @@ void QueueCoordinator::Run()
}
WaitJobs();
SavePartialState();
SaveAllPartialState();
debug("Exiting QueueCoordinator-loop");
}
@@ -500,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);
@@ -512,8 +524,7 @@ bool QueueCoordinator::GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &f
if (article->GetStatus() == ArticleInfo::aiUndefined)
{
articleInfo = article;
ok = true;
break;
return true;
}
}
@@ -525,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)
@@ -539,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);
@@ -600,6 +648,10 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* articleDownloader)
{
articleInfo->SetStatus(ArticleInfo::aiUndefined);
retry = true;
if (articleInfo->GetPartNumber() == 1)
{
nzbInfo->SetAllFirst(false);
}
}
if (!retry)
@@ -621,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;
@@ -636,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);
@@ -734,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);
@@ -767,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);
}
}
@@ -1194,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,6 +61,7 @@ private:
class CoordinatorDownloadQueue : public DownloadQueue
{
public:
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, const char* args);
@@ -73,23 +75,39 @@ 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();
};

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;
@@ -436,7 +437,11 @@ bool QueueEditor::InternEditList(ItemList* itemList,
{
EditGroup(item.m_nzbInfo, action, args);
}
break;
case DownloadQueue::eaGroupSortFiles:
SortGroupFiles(item.m_nzbInfo);
break;
default:
// suppress compiler warning "enumeration not handled in switch"
@@ -1259,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
@@ -68,6 +68,7 @@ private:
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,
@@ -493,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

@@ -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 = 0x6E7A6230; // = "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

@@ -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());

View File

@@ -53,12 +53,15 @@ private:
CString m_url;
EHttpMethod m_httpMethod;
EUserAccess m_userAccess;
bool m_rpcRequest;
bool m_authorized;
bool m_gzip;
CString m_origin;
int m_contentLen;
char m_authInfo[256+1];
char m_authToken[48+1];
static char m_serverAuthToken[3][48+1];
CString m_forwardedFor;
void Dispatch();
void SendAuthResponse();

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 @@
#include "DiskState.h"
#include "ScriptConfig.h"
#include "QueueScript.h"
#include "CommandScript.h"
extern void ExitProc();
extern void Reload();
@@ -306,6 +307,18 @@ private:
void PrintError(const char* errMsg);
};
class StartScriptXmlCommand : public XmlCommand
{
public:
virtual void Execute();
};
class LogScriptXmlCommand : public LogXmlCommand
{
protected:
virtual GuardedMessageList GuardMessages();
};
//*****************************************************************
// XmlRpcProcessor
@@ -709,6 +722,14 @@ std::unique_ptr<XmlCommand> XmlRpcProcessor::CreateCommand(const char* methodNam
{
command = std::make_unique<TestServerXmlCommand>();
}
else if (!strcasecmp(methodName, "startscript"))
{
command = std::make_unique<StartScriptXmlCommand>();
}
else if (!strcasecmp(methodName, "logscript"))
{
command = std::make_unique<LogScriptXmlCommand>();
}
else
{
command = std::make_unique<ErrorXmlCommand>(1, "Invalid procedure");
@@ -1978,6 +1999,10 @@ const char* ListGroupsXmlCommand::DetectStatus(NzbInfo* nzbInfo)
{
status = queueScriptActive ? "QS_EXECUTING" : "QS_QUEUED";
}
else if (nzbInfo->GetDirectUnpackStatus() == NzbInfo::nsRunning)
{
status = "UNPACKING";
}
else
{
status = postStageName[nzbInfo->GetPostInfo()->GetStage()];
@@ -2039,6 +2064,7 @@ EditCommandEntry EditCommandNameMap[] = {
{ DownloadQueue::eaGroupSetDupeScore, "GroupSetDupeScore" },
{ DownloadQueue::eaGroupSetDupeMode, "GroupSetDupeMode" },
{ DownloadQueue::eaGroupSort, "GroupSort" },
{ DownloadQueue::eaGroupSortFiles, "GroupSortFiles" },
{ DownloadQueue::eaPostDelete, "PostDelete" },
{ DownloadQueue::eaHistoryDelete, "HistoryDelete" },
{ DownloadQueue::eaHistoryFinalDelete, "HistoryFinalDelete" },
@@ -3109,7 +3135,7 @@ GuardedMessageList LogUpdateXmlCommand::GuardMessages()
return g_Maintenance->GuardMessages();
}
// struct[] servervolumes()
// struct[] servervolumes(bool BytesPerSeconds, bool BytesPerMinutes, bool BytesPerHours, bool BytesPerDays)
void ServerVolumesXmlCommand::Execute()
{
const char* XML_VOLUME_ITEM_START =
@@ -3180,6 +3206,10 @@ void ServerVolumesXmlCommand::Execute()
AppendResponse(IsJson() ? "[\n" : "<array><data>\n");
bool BytesPer[] = {true, true, true, true};
NextParamAsBool(&BytesPer[0]) && NextParamAsBool(&BytesPer[1]) &&
NextParamAsBool(&BytesPer[2]) && NextParamAsBool(&BytesPer[3]);
int index = 0;
for (ServerVolume& serverVolume : g_StatMeter->GuardServerVolumes())
@@ -3205,25 +3235,28 @@ void ServerVolumesXmlCommand::Execute()
for (int i=0; i<4; i++)
{
ServerVolume::VolumeArray* volumeArray = VolumeArrays[i];
const char* arrayName = VolumeNames[i];
AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_START : XML_BYTES_ARRAY_START, arrayName);
int index2 = 0;
for (int64 bytes : *volumeArray)
if (BytesPer[i])
{
uint32 sizeHi, sizeLo, sizeMB;
Util::SplitInt64(bytes, &sizeHi, &sizeLo);
sizeMB = (int)(bytes / 1024 / 1024);
ServerVolume::VolumeArray* volumeArray = VolumeArrays[i];
const char* arrayName = VolumeNames[i];
AppendCondResponse(",\n", IsJson() && index2++ > 0);
AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_ITEM : XML_BYTES_ARRAY_ITEM,
sizeLo, sizeHi, sizeMB);
AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_START : XML_BYTES_ARRAY_START, arrayName);
int index2 = 0;
for (int64 bytes : *volumeArray)
{
uint32 sizeHi, sizeLo, sizeMB;
Util::SplitInt64(bytes, &sizeHi, &sizeLo);
sizeMB = (int)(bytes / 1024 / 1024);
AppendCondResponse(",\n", IsJson() && index2++ > 0);
AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_ITEM : XML_BYTES_ARRAY_ITEM,
sizeLo, sizeHi, sizeMB);
}
AppendResponse(IsJson() ? JSON_BYTES_ARRAY_END : XML_BYTES_ARRAY_END);
AppendCondResponse(",\n", IsJson() && i < 3);
}
AppendResponse(IsJson() ? JSON_BYTES_ARRAY_END : XML_BYTES_ARRAY_END);
AppendCondResponse(",\n", IsJson() && i < 3);
}
AppendResponse(IsJson() ? JSON_VOLUME_ITEM_END : XML_VOLUME_ITEM_END);
index++;
@@ -3339,7 +3372,7 @@ void TestServerXmlCommand::Execute()
return;
}
NewsServer server(0, true, "test server", host, port, username, password, false, encryption, cipher, 1, 0, 0, 0, false);
NewsServer server(0, true, "test server", host, port, 0, username, password, false, encryption, cipher, 1, 0, 0, 0, false);
TestConnection connection(&server, this);
connection.SetTimeout(timeout == 0 ? g_Options->GetArticleTimeout() : timeout);
connection.SetSuppressErrors(false);
@@ -3359,3 +3392,45 @@ void TestServerXmlCommand::PrintError(const char* errMsg)
m_errText = EncodeStr(errMsg);
}
}
// bool startscript(string script, string command, string context, struct[] options);
void StartScriptXmlCommand::Execute()
{
if (!CheckSafeMethod())
{
return;
}
char* script;
char* command;
char* context;
if (!NextParamAsStr(&script) || !NextParamAsStr(&command) || !NextParamAsStr(&context))
{
BuildErrorResponse(2, "Invalid parameter");
return;
}
std::unique_ptr<Options::OptEntries> optEntries = std::make_unique<Options::OptEntries>();
char* name;
char* value;
char* dummy;
while ((IsJson() && NextParamAsStr(&dummy) && NextParamAsStr(&name) &&
NextParamAsStr(&dummy) && NextParamAsStr(&value)) ||
(!IsJson() && NextParamAsStr(&name) && NextParamAsStr(&value)))
{
DecodeStr(name);
DecodeStr(value);
optEntries->emplace_back(name, value);
}
bool ok = CommandScriptController::StartScript(script, command, std::move(optEntries));
BuildBoolResponse(ok);
}
// struct[] logscript(idfrom, entries)
GuardedMessageList LogScriptXmlCommand::GuardMessages()
{
return g_CommandScriptLog->GuardMessages();
}

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
@@ -417,7 +417,7 @@ CString FileSystem::MakeValidFilename(const char* filename, bool allowSlashes)
CString FileSystem::MakeUniqueFilename(const char* destDir, const char* basename)
{
CString result;
result.Format("%s%c%s", destDir, (int)PATH_SEPARATOR, basename);
result.Format("%s%c%s", destDir, PATH_SEPARATOR, basename);
int dupeNumber = 0;
while (FileExists(result))
@@ -442,12 +442,12 @@ CString FileSystem::MakeUniqueFilename(const char* destDir, const char* basename
}
}
result.Format("%s%c%s.duplicate%d%s", destDir, (int)PATH_SEPARATOR,
result.Format("%s%c%s.duplicate%d%s", destDir, PATH_SEPARATOR,
*filenameWithoutExt, dupeNumber, extension);
}
else
{
result.Format("%s%c%s.duplicate%d", destDir, (int)PATH_SEPARATOR,
result.Format("%s%c%s.duplicate%d", destDir, PATH_SEPARATOR,
basename, dupeNumber);
}
}
@@ -527,13 +527,6 @@ bool FileSystem::FileExists(const char* filename)
#endif
}
bool FileSystem::FileExists(const char* path, const char* filenameWithoutPath)
{
BString<1024> fullFilename("%s%c%s", path, (int)PATH_SEPARATOR, filenameWithoutPath);
bool exists = FileExists(fullFilename);
return exists;
}
bool FileSystem::DirectoryExists(const char* dirFilename)
{
#ifdef WIN32
@@ -580,6 +573,37 @@ bool FileSystem::RemoveDirectory(const char* dirFilename)
#endif
}
/* Delete directory which is empty or contains only hidden files or directories (whose names start with dot) */
bool FileSystem::DeleteDirectory(const char* dirFilename)
{
if (RemoveDirectory(dirFilename))
{
return true;
}
// check if directory contains only hidden files (whose names start with dot)
{
DirBrowser dir(dirFilename);
while (const char* filename = dir.Next())
{
if (*filename != '.')
{
// calling RemoveDirectory to set correct errno
return RemoveDirectory(dirFilename);
}
}
} // make sure "DirBrowser dir" is destroyed (and has closed its handle) before we trying to delete the directory
CString errmsg;
if (!DeleteDirectoryWithContent(dirFilename, errmsg))
{
// calling RemoveDirectory to set correct errno
return RemoveDirectory(dirFilename);
}
return true;
}
bool FileSystem::DeleteDirectoryWithContent(const char* dirFilename, CString& errmsg)
{
errmsg.Clear();
@@ -999,7 +1023,7 @@ CString FileSystem::WidePathToUtfPath(const wchar_t* wpath)
#ifdef WIN32
DirBrowser::DirBrowser(const char* path)
{
BString<1024> mask("%s%c*.*", path, (int)PATH_SEPARATOR);
BString<1024> mask("%s%c*.*", path, PATH_SEPARATOR);
m_file = FindFirstFileW(FileSystem::UtfPathToWidePath(mask), &m_findData);
m_first = true;
}

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
@@ -41,10 +41,15 @@ public:
static bool CopyFile(const char* srcFilename, const char* dstFilename);
static bool DeleteFile(const char* filename);
static bool FileExists(const char* filename);
static bool FileExists(const char* path, const char* filenameWithoutPath);
static bool DirectoryExists(const char* dirFilename);
static bool CreateDirectory(const char* dirFilename);
/* Delete empty directory */
static bool RemoveDirectory(const char* dirFilename);
/* Delete directory which is empty or contains only hidden files or directories */
static bool DeleteDirectory(const char* dirFilename);
static bool DeleteDirectoryWithContent(const char* dirFilename, CString& errmsg);
static bool ForceDirectories(const char* path, CString& errmsg);
static CString GetCurrentDirectory();

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
@@ -198,14 +198,15 @@ void ScriptController::PrepareEnvOptions(const char* stripPrefix)
for (Options::OptEntry& optEntry : g_Options->GuardOptEntries())
{
const char* value = GetOptValue(optEntry.GetName(), optEntry.GetValue());
if (stripPrefix && !strncmp(optEntry.GetName(), stripPrefix, prefixLen) &&
(int)strlen(optEntry.GetName()) > prefixLen)
{
SetEnvVarSpecial("NZBPO", optEntry.GetName() + prefixLen, optEntry.GetValue());
SetEnvVarSpecial("NZBPO", optEntry.GetName() + prefixLen, value);
}
else if (!stripPrefix)
{
SetEnvVarSpecial("NZBOP", optEntry.GetName(), optEntry.GetValue());
SetEnvVarSpecial("NZBOP", optEntry.GetName(), value);
}
}
}
@@ -287,6 +288,7 @@ void ScriptController::PrepareArgs()
if (FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, command, 0, 0,
m_cmdLine, sizeof(m_cmdLine), (va_list*)args))
{
Util::TrimRight(Util::ReduceStr(m_cmdLine, "*", ""));
debug("CmdLine: %s", m_cmdLine);
return;
}
@@ -313,7 +315,8 @@ int ScriptController::Execute()
{
#endif
int pipein = StartProcess();
int pipein = -1, pipeout = -1;
StartProcess(&pipein, &pipeout);
if (pipein == -1)
{
m_completed = true;
@@ -324,12 +327,28 @@ int ScriptController::Execute()
m_readpipe = fdopen(pipein, "r");
if (!m_readpipe)
{
PrintMessage(Message::mkError, "Could not open pipe to %s", *m_infoName);
PrintMessage(Message::mkError, "Could not open read pipe to %s", *m_infoName);
close(pipein);
close(pipeout);
m_completed = true;
return -1;
}
m_writepipe = 0;
if (m_needWrite)
{
// open the write end
m_writepipe = fdopen(pipeout, "w");
if (!m_writepipe)
{
PrintMessage(Message::mkError, "Could not open write pipe to %s", *m_infoName);
close(pipein);
close(pipeout);
m_completed = true;
return -1;
}
}
#ifdef CHILD_WATCHDOG
debug("Creating child watchdog");
ChildWatchDog watchDog;
@@ -384,6 +403,11 @@ int ScriptController::Execute()
fclose(m_readpipe);
}
if (m_writepipe)
{
fclose(m_writepipe);
}
if (m_terminated && m_infoName)
{
warn("Interrupted %s", *m_infoName);
@@ -391,7 +415,7 @@ int ScriptController::Execute()
exitCode = 0;
if (!m_terminated && !m_detached)
if (!m_detached)
{
exitCode = WaitProcess();
#ifndef WIN32
@@ -430,7 +454,7 @@ void ScriptController::BuildCommandLine(char* cmdLineBuf, int bufSize)
/*
* Returns file descriptor of the read-pipe or -1 on error.
*/
int ScriptController::StartProcess()
void ScriptController::StartProcess(int* pipein, int* pipeout)
{
CString workingDir = m_workingDir;
if (workingDir.Empty())
@@ -456,21 +480,27 @@ int ScriptController::StartProcess()
}
// create pipes to write and read data
HANDLE readPipe, writePipe;
HANDLE readPipe, readProcPipe;
HANDLE writePipe = 0, writeProcPipe = 0;
SECURITY_ATTRIBUTES securityAttributes = { 0 };
securityAttributes.nLength = sizeof(securityAttributes);
securityAttributes.bInheritHandle = TRUE;
CreatePipe(&readPipe, &writePipe, &securityAttributes, 0);
CreatePipe(&readPipe, &readProcPipe, &securityAttributes, 0);
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
if (m_needWrite)
{
CreatePipe(&writeProcPipe, &writePipe, &securityAttributes, 0);
SetHandleInformation(writePipe, HANDLE_FLAG_INHERIT, 0);
}
STARTUPINFOW startupInfo = { 0 };
startupInfo.cb = sizeof(startupInfo);
startupInfo.dwFlags = STARTF_USESTDHANDLES;
startupInfo.hStdInput = 0;
startupInfo.hStdOutput = writePipe;
startupInfo.hStdError = writePipe;
startupInfo.hStdInput = writeProcPipe;
startupInfo.hStdOutput = readProcPipe;
startupInfo.hStdError = readProcPipe;
PROCESS_INFORMATION processInfo = { 0 };
@@ -501,8 +531,10 @@ int ScriptController::StartProcess()
PrintMessage(Message::mkError, "Could not build short path for %s", workingDir);
}
CloseHandle(readPipe);
CloseHandle(readProcPipe);
CloseHandle(writePipe);
return -1;
CloseHandle(writeProcPipe);
return;
}
debug("Child Process-ID: %i", (int)processInfo.dwProcessId);
@@ -510,24 +542,37 @@ int ScriptController::StartProcess()
m_processId = processInfo.hProcess;
m_dwProcessId = processInfo.dwProcessId;
// close unused "write" end
CloseHandle(writePipe);
// close unused pipe ends
CloseHandle(readProcPipe);
CloseHandle(writeProcPipe);
int pipein = _open_osfhandle((intptr_t)readPipe, _O_RDONLY);
return pipein;
*pipein = _open_osfhandle((intptr_t)readPipe, _O_RDONLY);
if (m_needWrite)
{
*pipeout = _open_osfhandle((intptr_t)writePipe, _O_WRONLY);
}
#else
int p[2];
int pipein;
int pipeout;
int pin[] = {0, 0};
int pout[] = {0, 0};
// create the pipe
if (pipe(p))
// create the pipes
if (pipe(pin))
{
PrintMessage(Message::mkError, "Could not open pipe: errno %i", errno);
return -1;
PrintMessage(Message::mkError, "Could not open read pipe: errno %i", errno);
return;
}
if (m_needWrite && pipe(pout))
{
PrintMessage(Message::mkError, "Could not open write pipe: errno %i", errno);
close(pin[0]);
close(pin[1]);
return;
}
*pipein = pin[0];
*pipeout = pout[1];
std::vector<char*> environmentStrings = m_environmentStrings.GetStrings();
char** envdata = environmentStrings.data();
@@ -537,18 +582,20 @@ int ScriptController::StartProcess()
args.emplace_back(nullptr);
char* const* argdata = (char* const*)args.data();
pipein = p[0];
pipeout = p[1];
debug("forking");
pid_t pid = fork();
if (pid == -1)
{
PrintMessage(Message::mkError, "Could not start %s: errno %i", *m_infoName, errno);
close(pipein);
close(pipeout);
return -1;
close(pin[0]);
close(pin[1]);
if (m_needWrite)
{
close(pout[0]);
close(pout[1]);
}
return;
}
else if (pid == 0)
{
@@ -561,14 +608,19 @@ int ScriptController::StartProcess()
// create new process group (see Terminate() where it is used)
setsid();
// close up the "read" end
close(pipein);
// make the pipeout to be the same as stdout and stderr
dup2(pipeout, 1);
dup2(pipeout, 2);
dup2(pin[1], 1);
dup2(pin[1], 2);
close(pin[0]);
close(pin[1]);
close(pipeout);
if (m_needWrite)
{
// make the pipein to be the same as stdin
dup2(pout[0], 0);
close(pout[0]);
close(pout[1]);
}
#ifdef CHILD_WATCHDOG
write(1, "\n", 1);
@@ -608,17 +660,20 @@ int ScriptController::StartProcess()
m_processId = pid;
// close unused "write" end
close(pipeout);
// close unused pipe ends
close(pin[1]);
if (m_needWrite)
{
close(pout[0]);
}
#endif
return pipein;
}
int ScriptController::WaitProcess()
{
#ifdef WIN32
WaitForSingleObject(m_processId, INFINITE);
// wait max 60 seconds for terminated processes
WaitForSingleObject(m_processId, m_terminated ? 60 * 1000 : INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(m_processId, &exitCode);
return exitCode;
@@ -641,17 +696,6 @@ void ScriptController::Terminate()
#ifdef WIN32
BOOL ok = TerminateProcess(m_processId, -1) || m_completed;
if (ok)
{
// wait 60 seconds for process to terminate
WaitForSingleObject(m_processId, 60 * 1000);
}
else
{
DWORD exitCode = 0;
GetExitCodeProcess(m_processId, &exitCode);
ok = exitCode != STILL_ACTIVE;
}
#else
pid_t killId = m_processId;
if (getpgid(killId) == killId)
@@ -822,3 +866,9 @@ void ScriptController::PrintMessage(Message::EKind kind, const char* format, ...
AddMessage(kind, tmp2);
}
}
void ScriptController::Write(const char* str)
{
fwrite(str, 1, strlen(str), m_writepipe);
fflush(m_writepipe);
}

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
@@ -78,8 +78,11 @@ protected:
void ResetEnv();
void PrepareEnvOptions(const char* stripPrefix);
void PrepareArgs();
int StartProcess();
virtual const char* GetOptValue(const char* name, const char* value) { return value; }
void StartProcess(int* pipein, int* pipeout);
int WaitProcess();
void SetNeedWrite(bool needWrite) { m_needWrite = needWrite; }
void Write(const char* str);
#ifdef WIN32
void BuildCommandLine(char* cmdLineBuf, int bufSize);
#endif
@@ -94,7 +97,9 @@ private:
bool m_terminated = false;
bool m_completed = false;
bool m_detached = false;
FILE* m_readpipe;
bool m_needWrite = false;
FILE* m_readpipe = 0;
FILE* m_writepipe = 0;
#ifdef WIN32
HANDLE m_processId = 0;
DWORD m_dwProcessId = 0;

View File

@@ -169,13 +169,15 @@ void* Thread::thread_handler(void* object)
debug("Thread-func exited");
thread->m_running = false;
if (thread->m_autoDestroy)
{
debug("Autodestroying Thread-object");
delete thread;
}
else
{
thread->m_running = false;
}
{
Guard guard(m_threadMutex);

View File

@@ -95,7 +95,6 @@ public:
bool IsStopped() { return m_stopped; };
bool IsRunning() const { return m_running; }
void SetRunning(bool onOff) { m_running = onOff; }
bool GetAutoDestroy() { return m_autoDestroy; }
void SetAutoDestroy(bool autoDestroy) { m_autoDestroy = autoDestroy; }
static int GetThreadCount();

View File

@@ -429,6 +429,49 @@ std::vector<CString> Util::SplitStr(const char* str, const char* separators)
return result;
}
bool Util::EndsWith(const char* str, const char* suffix, bool caseSensitive)
{
if (!str)
{
return false;
}
if (EmptyStr(suffix))
{
return true;
}
int lenStr = strlen(str);
int lenSuf = strlen(suffix);
if (lenSuf > lenStr)
{
return false;
}
if (caseSensitive)
{
return !strcmp(str + lenStr - lenSuf, suffix);
}
else
{
return !strcasecmp(str + lenStr - lenSuf, suffix);
}
}
bool Util::AlphaNum(const char* str)
{
for (const char* p = str; *p; p++)
{
char ch = *p;
if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')))
{
return false;
}
}
return true;
}
/* Calculate Hash using Bob Jenkins (1996) algorithm
* http://burtleburtle.net/bob/c/lookup2.c
*/

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
@@ -49,6 +49,8 @@ public:
static char* Trim(char* str);
static bool EmptyStr(const char* str) { return !str || !*str; }
static std::vector<CString> SplitStr(const char* str, const char* separators);
static bool EndsWith(const char* str, const char* suffix, bool caseSensitive);
static bool AlphaNum(const char* str);
/* replace all occurences of szFrom to szTo in string szStr with a limitation that szTo must be shorter than szFrom */
static char* ReduceStr(char* str, const char* from, const char* to);

View File

@@ -71,29 +71,11 @@ BOOL WINAPI WinConsole::ConsoleCtrlHandler(DWORD dwCtrlType)
}
}
WinConsole::WinConsole()
{
m_initialArguments = nullptr;
m_initialArgumentCount = 0;
m_defaultArguments = nullptr;
m_iconData = nullptr;
m_modal = false;
m_autostart = false;
m_showTrayIcon = true;
m_showConsole = false;
m_showWebUI = true;
m_autoParam = false;
m_doubleClick = false;
m_trayWindow = 0;
m_running = false;
m_runningService = false;
}
WinConsole::~WinConsole()
{
if (m_initialArguments)
{
g_Arguments = (char*(*)[])m_initialArguments;
g_Arguments = (char*(*)[])m_initialArguments;
g_ArgumentCount = m_initialArgumentCount;
}
free(m_defaultArguments);
@@ -107,7 +89,6 @@ void WinConsole::InitAppMode()
m_instance = (HINSTANCE)GetModuleHandle(0);
DWORD processId;
GetWindowThreadProcessId(GetConsoleWindow(), &processId);
m_appMode = false;
if (GetCurrentProcessId() == processId && g_ArgumentCount == 1)
{
@@ -131,24 +112,27 @@ void WinConsole::InitAppMode()
{
break;
}
if (!strcmp((*g_Arguments)[i], "-app"))
else if (!strcmp((*g_Arguments)[i], "-app"))
{
m_appMode = true;
}
if (!strcmp((*g_Arguments)[i], "-auto"))
else if (!strcmp((*g_Arguments)[i], "-auto"))
{
m_autoParam = true;
}
else if (!strcmp((*g_Arguments)[i], "-A"))
{
m_addParam = true;
}
}
if (m_appMode)
if (m_appMode || m_autoParam)
{
m_initialArguments = (char**)g_Arguments;
m_initialArgumentCount = g_ArgumentCount;
// remove "-app" from command line
int argc = g_ArgumentCount - 1 - (m_autoParam ? 1 : 0);
int argc = g_ArgumentCount - (m_appMode ? 1 : 0) - (m_autoParam ? 1 : 0);
m_defaultArguments = (char**)malloc(sizeof(char*) * (argc + 2));
int p = 0;
@@ -166,7 +150,13 @@ void WinConsole::InitAppMode()
}
}
// m_bAppMode indicates whether the program was started as a standalone app
if (m_addParam)
{
RunAnotherInstance();
return;
}
// m_appMode indicates whether the program was started as a standalone app
// (not from a dos box window). In that case we hide the console window,
// show the tray icon and start in server mode
@@ -179,7 +169,7 @@ void WinConsole::InitAppMode()
void WinConsole::Run()
{
if (!m_appMode)
if (!m_appMode || m_addParam)
{
return;
}
@@ -1043,3 +1033,12 @@ void WinConsole::ResetFactoryDefaults()
mayStartBrowser = true;
Reload();
}
void WinConsole::RunAnotherInstance()
{
ShowWindow(GetConsoleWindow(), SW_HIDE);
if (!(FindWindow("NZBGet tray window", nullptr) || IsServiceRunning()))
{
ShellExecute(0, "open", (*g_Arguments)[0], nullptr, nullptr, SW_SHOWNORMAL);
}
}

View File

@@ -26,7 +26,6 @@
class WinConsole : public Thread
{
public:
WinConsole();
~WinConsole();
virtual void Stop();
void InitAppMode();
@@ -37,16 +36,16 @@ protected:
virtual void Run();
private:
bool m_appMode;
char** m_defaultArguments;
char** m_initialArguments;
int m_initialArgumentCount;
HWND m_trayWindow;
NOTIFYICONDATA* m_iconData;
bool m_appMode = false;
char** m_defaultArguments = nullptr;
char** m_initialArguments = nullptr;
int m_initialArgumentCount = 0;
HWND m_trayWindow = 0;
NOTIFYICONDATA* m_iconData = nullptr;
UINT m_taskbarCreatedMessage;
HMENU m_menu;
HINSTANCE m_instance;
bool m_modal;
bool m_modal = false;
HFONT m_linkFont;
HFONT m_nameFont;
HFONT m_titleFont;
@@ -56,14 +55,15 @@ private:
HICON m_idleIcon;
HICON m_workingIcon;
HICON m_pausedIcon;
bool m_autostart;
bool m_showTrayIcon;
bool m_showConsole;
bool m_showWebUI;
bool m_autoParam;
bool m_running;
bool m_runningService;
bool m_doubleClick;
bool m_autostart = false;
bool m_showTrayIcon = true;
bool m_showConsole = false;
bool m_showWebUI = true;
bool m_autoParam = false;
bool m_addParam = false;
bool m_running = false;
bool m_runningService = false;
bool m_doubleClick = false;
void CreateResources();
void CreateTrayIcon();
@@ -82,9 +82,9 @@ private:
void BuildMenu();
void ShowCategoryDir(int catIndex);
void SetupConfigFile();
void SetupScripts();
void ShowFactoryResetDialog();
void ResetFactoryDefaults();
void RunAnotherInstance();
static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType);
static LRESULT CALLBACK TrayWndProcStat(HWND hwndWin, UINT uMsg, WPARAM wParam, LPARAM lParam);

View File

@@ -2,7 +2,7 @@
#
# This file is part of nzbget
#
# Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
# Copyright (C) 2015-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
@@ -177,6 +177,11 @@ UpdateFromRepository()
git checkout $REVISION
fi
touch Makefile.in configure config.h.in
echo "Updating root certificates"
cd ../setup
curl --remote-name --time-cond cacert.pem https://curl.haxx.se/ca/cacert.pem
cd $BUILDDIR
fi
}
@@ -278,29 +283,34 @@ ConfigureTarget()
STRIP="-s"
fi
DEBUG=""
if [ $CONFIG != "release" ]; then
DEBUG="-g"
fi
case "$TOOLKIND-$CONFIG" in
buildroot-debug|buildroot-debug-strip)
LIBPREF="$STAGING" LDFLAGS="-static $STRIP" \
LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -g -fasynchronous-unwind-tables" \
LIBS="-lcrypto -ldl -lz -lubacktrace" \
./configure --disable-dependency-tracking --host=$ARCH-$TOOLNAME --enable-debug
;;
buildroot-release|buildroot-release-nostrip)
LIBPREF="$STAGING" LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -O2" \
LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -O2 $DEBUG" \
LIBS="-lcrypto -ldl -lz" \
./configure --disable-dependency-tracking --host=$ARCH-$TOOLNAME
;;
crossgcc-debug|crossgcc-debug-strip)
LIBPREF="$STAGING" LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -g -fasynchronous-unwind-tables" \
PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" \
LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -g -fasynchronous-unwind-tables -I$STAGING/include" \
PKG_CONFIG_LIBDIR="$STAGING/lib/pkgconfig" \
./configure --disable-dependency-tracking --host=$ARCH-$TOOLNAME --enable-debug
;;
crossgcc-release|crossgcc-release-nostrip)
LIBPREF="$STAGING" LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -O2" \
PKG_CONFIG_PATH="$STAGING/lib/pkgconfig" \
LDFLAGS="-static $STRIP" \
CXXFLAGS="-std=c++14 -O2 $DEBUG -I$STAGING/include" \
PKG_CONFIG_LIBDIR="$STAGING/lib/pkgconfig" \
./configure --disable-dependency-tracking --host=$ARCH-$TOOLNAME
;;
esac
@@ -412,6 +422,8 @@ BuildInstaller()
# adjusting nzbget.conf
sed 's:^UnrarCmd=unrar:UnrarCmd=${AppDir}/unrar:' -i nzbget/webui/nzbget.conf.template
sed 's:^SevenZipCmd=7z:SevenZipCmd=${AppDir}/7za:' -i nzbget/webui/nzbget.conf.template
sed 's:^CertStore=.*:CertStore=${AppDir}/cacert.pem:' -i nzbget/webui/nzbget.conf.template
sed 's:^CertCheck=.*:CertCheck=yes:' -i nzbget/webui/nzbget.conf.template
INSTFILE=$BASENAME-bin-$PLATFORM$SUFFIX.run
@@ -422,6 +434,7 @@ BuildInstaller()
cp $BUILDDIR/pubkey.pem nzbget
cp ../setup/license-unrar.txt nzbget
cp ../setup/license-7zip.txt nzbget
cp ../setup/cacert.pem nzbget
# adjusting update config file
sed "s:linux:$PLATFORM:" -i nzbget/webui/package-info.json

View File

@@ -142,13 +142,16 @@ for TARGET in $TARGETS; do
unrar)
tar xzf unrarsrc-*.tar.gz
cd unrar
sed 's:^CXX=g++:#CXX=g++:' -i makefile
sed 's:^CXX=:#CXX=:' -i makefile
sed 's:^STRIP=strip:#STRIP=strip:' -i makefile
sed 's:^LDFLAGS=:LDFLAGS=-static :' -i makefile
sed 's:^CXXFLAGS=-O2:#CXXFLAGS=-O2:' -i makefile
if test "$ENDIAN" = "big"; then
if [ "$ENDIAN" = "big" ]; then
sed 's:^DEFINES=:DEFINES=-DBIG_ENDIAN :' -i makefile
fi
if [ "$PLATSUFF" != "-bsd" ]; then
sed 's:^DEFINES=:DEFINES=-DUSE_FALLOCATE :' -i makefile
fi
EXEDIR=
LICENSE=license.txt

View File

@@ -67,7 +67,7 @@ WebDir=
#
# This option may contain multiple directories separated with commas or semicolons.
#
# NOTE: For information on writing scripts visit http://nzbget.net/Extension_scripts.
# NOTE: For information on writing scripts visit http://nzbget.net/extension-scripts.
ScriptDir=${MainDir}/scripts
# Lock-file for daemon-mode, POSIX only.
@@ -116,6 +116,22 @@ ConfigTemplate=
# available on program start.
RequiredDir=
# Certificate store file or directory.
#
# Certificate store contains root certificates used for server certificate
# verification when connecting to servers with encryption (TLS/SSL). This
# includes communication with news-servers for article downloading and
# with web-servers (via https) for fetching of rss feeds and nzb-files.
#
# The option can point either to one big file containing all root
# certificates or to a directory containing certificate files, in PEM format.
#
# Example: /etc/ssl/certs/ca-certificates.crt.
#
# NOTE: Certificate verification must be enabled separately via option <CertCheck>.
#
# NOTE: For more details visit http://nzbget.net/certificate-verification.
CertStore=
##############################################################################
### NEWS-SERVERS ###
@@ -207,7 +223,7 @@ Server1.Encryption=no
# chooses the cipher automatically. To achieve the best performance
# however you can manually select a faster cipher.
#
# See http://nzbget.net/Choosing_a_cipher for details.
# See http://nzbget.net/choosing-cipher for details.
#
# NOTE: One of the fastest cipher is RC4. To select it use the cipher string
# "RC4-MD5" (if NZBGet was configured to use OpenSSL) or
@@ -234,6 +250,15 @@ Server1.Connections=4
# Value "0" disables retention check.
Server1.Retention=0
# IP protocol version (auto, ipv4, ipv6).
Server1.IpVersion=auto
# User comments on this server.
#
# Any text you want to save along with the server definition. For your convenience
# or for usage in custom extension scripts.
Server1.Notes=
# Second server, on level 0.
#Server2.Level=0
@@ -342,6 +367,16 @@ AddUsername=
# Set to empty value to disable password check.
AddPassword=
# Authenticate using web-form (yes, no).
#
# The preferred and default way to authenticate in web-interface is using
# HTTP authentication. Web-browsers show a special dialog to enter username
# and password which they then send back to NZBGet. Sometimes browser plugins
# aided at storing and filling of passwords do not work properly with browser's
# built-in dialog. To help with such tools NZBGet provide an alternative
# authentication mechanism via web form.
FormAuth=no
# Secure control of NZBGet server (yes, no).
#
# Activate the option if you want to access NZBGet built-in web-server
@@ -371,6 +406,27 @@ SecureKey=
# web-server because all requests will have the address of this server.
AuthorizedIP=
# TLS certificate verification (yes, no).
#
# 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
# should be 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.
#
# Sometimes servers are improperly configured and the certificate verification
# fails even if there is no hacker attack in place. In that case you should
# inform the server owner about the issue. If you still need to connect to
# servers with invalid certificates you can disable the certificate verification
# but you should know that your connection is insecure and you might be
# connecting to attacker's server without your awareness.
#
# NOTE: Certificate verification requires a list of trusted root certificates,
# which must be configured using option <CertStore>.
#
# NOTE: For more details visit http://nzbget.net/certificate-verification.
CertCheck=no
# User name for daemon-mode, POSIX only.
#
# Set the user that the daemon normally runs at (POSIX in daemon-mode only).
@@ -463,7 +519,7 @@ Category4.Name=Software
#
# MORE INFO:
# NOTE: This is a short documentation, for more information visit
# http://nzbget.net/RSS.
# http://nzbget.net/rss.
#
# Feed filter consists of rules - one rule per line. Each rule defines
# a search string and a command, which must be performed if the search
@@ -586,7 +642,7 @@ Category4.Name=Software
# 5) Q: HDTV.
#
# NOTE: This is a short documentation, for more information visit
# http://nzbget.net/RSS.
# http://nzbget.net/rss.
#Feed1.Filter=
# How often to check for new items (minutes).
@@ -628,7 +684,7 @@ Category4.Name=Software
# scripts must be stored in directory set by option <ScriptDir> and
# paths relative to <ScriptDir> must be entered here.
#
# NOTE: For developer documentation visit http://nzbget.net/Extension_scripts.
# NOTE: For developer documentation visit http://nzbget.net/extension-scripts.
#Feed1.Extensions=
@@ -676,7 +732,7 @@ NzbDirFileAge=60
# set to "Delete", "Park" or "None". If it is set to "Pause" you will need to
# manually unpause another duplicate (if any exists in queue).
#
# NOTE: For more info on duplicates see http://nzbget.net/RSS.
# NOTE: For more info on duplicates see http://nzbget.net/rss.
DupeCheck=yes
@@ -815,6 +871,28 @@ WriteBuffer=0
# slow CPU disabling of CRC-Check may improve performance.
CrcCheck=yes
# How to name downloaded files (auto, article, nzb).
#
# Article - use file names stored in article metadata;
# Nzb - use file names as defined in nzb-file;
# Auto - prefer names from article metadata; for obfuscated files use
# names from nzb-file.
#
# NOTE: This option sets the naming convention for files listed in nzb. It has not
# effect on files extracted from archives.
FileNaming=auto
# Reorder files within nzbs for optimal download order (yes, no).
#
# When nzb-file is added to queue the files listed within nzb can be in a random
# order. When "ReorderFiles" is active the files are automatically sorted
# alphabetically to ensure download of archive parts in correct order. The
# par2-files are moved to the end and then sorted by size.
#
# NOTE: When option <DirectRename> is active the files are sorted again after the file
# names become known.
ReorderFiles=yes
# Post-processing strategy (sequential, balanced, aggressive, rocket).
#
# Sequential - downloaded items are post processed from a queue, one item at a
@@ -1051,13 +1129,20 @@ NzbLog=yes
# the names of broken files.
BrokenLog=yes
# Create memory dump (core-file) on abnormal termination, Linux only (yes, no).
# Print call stack trace into log on program crash (Linux and Windows) (yes, no).
#
# Core-files are very helpful for debugging.
# Call stack traces are very helpful for debugging. Call stack traces can be
# printed only when the program was compiled in debug mode.
CrashTrace=yes
# Save memory dump into disk on program crash (Linux only) (yes, no).
#
# NOTE: Core-files may contain sensitive data, like your login/password to
# newsserver etc.
DumpCore=no
# Memory dumps (core-files) are very helpful for debugging, especially if
# they were produced by the program compiled in debug mode.
#
# NOTE: Memory dumps may contain sensitive data, like your login/password
# to news-server etc.
CrashDump=no
# Local time correction (hours or minutes).
#
@@ -1164,13 +1249,14 @@ UpdateInterval=200
#
# Some scheduler commands require additional parameters:
# DownloadRate - download rate limit to be set (kilobytes/sec).
# Example: 1000;
# Example: 1000.
# NOTE: use value "0" to disable download limit (unlimited speed).
# Script - list of scheduler scripts to execute. The scripts in the
# list must be separated with commas or semicolons. All
# scripts must be stored in directory set by option
# <ScriptDir> and paths relative to <ScriptDir> must be
# entered here. For developer documentation visit
# http://nzbget.net/Extension_scripts;
# http://nzbget.net/extension-scripts;
# Process - path to the program to execute and its parameters.
# Example: /home/user/fetch.sh.
# If filename or any parameter contains spaces it
@@ -1321,6 +1407,14 @@ ParRename=yes
# would fail otherwise.
RarRename=yes
# Directly rename files during downloading (yes, no).
#
# This is similar to par-renaming (option <ParRename>) but the files
# are renamed during downloading instead of post-processing stage. This
# requires some tricky handling of files and works only for healthy
# downloads.
DirectRename=no
# What to do if download health drops below critical health (delete, park,
# pause, none).
#
@@ -1393,6 +1487,16 @@ ParPauseQueue=no
# is performed and the unpack is executed again.
Unpack=yes
# Directly unpack files during downloading (yes, no).
#
# When active the files are unpacked during downloading instead of post-processing
# stage. This works only for healthy downloads. Damaged downloads are unpacked
# as usual during post-processing stage after par-repair.
#
# NOTE: This option requires unpack to be enabled in general via option <Unpack>.
# NOTE: For best results also activate option <DirectRename> and option <ReorderFiles>.
DirectUnpack=no
# Pause download queue during unpack (yes, no).
#
# Enable the option to give CPU more time for unpacking. That helps
@@ -1524,9 +1628,9 @@ UnpackPassFile=
# by their order in option <Extensions>.
#
# NOTE: For the list of interesting extension scripts see
# http://nzbget.net/Catalog_of_post-processing_scripts.
# http://nzbget.net/catalog-of-extension-scripts.
#
# NOTE: For developer documentation visit http://nzbget.net/Extension_scripts.
# NOTE: For developer documentation visit http://nzbget.net/extension-scripts.
Extensions=
# Execution order for extension scripts.

View File

@@ -52,7 +52,7 @@
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nserv;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="18.1";_DEBUG;_CONSOLE;DEBUG;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="19.1";_DEBUG;_CONSOLE;DEBUG;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@@ -72,7 +72,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nserv;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="18.1";NDEBUG;_CONSOLE;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="19.1";NDEBUG;_CONSOLE;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>Sync</ExceptionHandling>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>Use</PrecompiledHeader>
@@ -96,6 +96,7 @@
<ClCompile Include="daemon\connect\TlsSocket.cpp" />
<ClCompile Include="daemon\connect\WebDownloader.cpp" />
<ClCompile Include="daemon\extension\FeedScript.cpp" />
<ClCompile Include="daemon\extension\CommandScript.cpp" />
<ClCompile Include="daemon\extension\NzbScript.cpp" />
<ClCompile Include="daemon\extension\PostScript.cpp" />
<ClCompile Include="daemon\extension\QueueScript.cpp" />
@@ -140,6 +141,8 @@
<ClCompile Include="daemon\postprocess\RarRenamer.cpp" />
<ClCompile Include="daemon\postprocess\Rename.cpp" />
<ClCompile Include="daemon\postprocess\Unpack.cpp" />
<ClCompile Include="daemon\postprocess\DirectUnpack.cpp" />
<ClCompile Include="daemon\queue\DirectRenamer.cpp" />
<ClCompile Include="daemon\queue\DiskState.cpp" />
<ClCompile Include="daemon\queue\DownloadInfo.cpp" />
<ClCompile Include="daemon\queue\DupeCoordinator.cpp" />
@@ -193,6 +196,7 @@
<ClInclude Include="daemon\connect\TlsSocket.h" />
<ClInclude Include="daemon\connect\WebDownloader.h" />
<ClInclude Include="daemon\extension\FeedScript.h" />
<ClInclude Include="daemon\extension\CommandScript.h" />
<ClInclude Include="daemon\extension\NzbScript.h" />
<ClInclude Include="daemon\extension\PostScript.h" />
<ClInclude Include="daemon\extension\QueueScript.h" />
@@ -237,6 +241,8 @@
<ClInclude Include="daemon\postprocess\RarRenamer.h" />
<ClInclude Include="daemon\postprocess\Rename.h" />
<ClInclude Include="daemon\postprocess\Unpack.h" />
<ClInclude Include="daemon\postprocess\DirectUnpack.h" />
<ClInclude Include="daemon\queue\DirectRenamer.h" />
<ClInclude Include="daemon\queue\DiskState.h" />
<ClInclude Include="daemon\queue\DownloadInfo.h" />
<ClInclude Include="daemon\queue\DupeCoordinator.h" />

View File

@@ -2,7 +2,7 @@
#
# E-Mail post-processing script for NZBGet
#
# 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
@@ -60,6 +60,9 @@
# SMTP server password, if required.
#Password=mypass
# To check connection parameters click the button.
#ConnectionTest@Send Test E-Mail
# Append statistics to the message (yes, no).
#Statistics=yes
@@ -114,38 +117,46 @@ for optname in required_options:
print('[ERROR] Option %s is missing in configuration file. Please check script settings' % optname[6:])
sys.exit(POSTPROCESS_ERROR)
status = os.environ['NZBPP_STATUS']
total_status = os.environ['NZBPP_TOTALSTATUS']
# Check if the script is executed from settings page with a custom command
command = os.environ.get('NZBCP_COMMAND')
test_mode = command == 'ConnectionTest'
if command != None and not test_mode:
print('[ERROR] Invalid command ' + command)
sys.exit(POSTPROCESS_ERROR)
status = os.environ.get('NZBPP_STATUS') if not test_mode else 'SUCCESS/ALL'
total_status = os.environ.get('NZBPP_TOTALSTATUS') if not test_mode else 'SUCCESS'
# If any script fails the status of the item in the history is "WARNING/SCRIPT".
# This status however is not passed to pp-scripts in the env var "NZBPP_STATUS"
# because most scripts are independent of each other and should work even
# if a previous script has failed. But not in the case of E-Mail script,
# which should take the status of the previous scripts into account as well.
if total_status == 'SUCCESS' and os.environ['NZBPP_SCRIPTSTATUS'] == 'FAILURE':
if total_status == 'SUCCESS' and os.environ.get('NZBPP_SCRIPTSTATUS') == 'FAILURE':
total_status = 'WARNING'
status = 'WARNING/SCRIPT'
success = total_status == 'SUCCESS'
if success and os.environ.get('NZBPO_SENDMAIL') == 'OnFailure':
if success and os.environ.get('NZBPO_SENDMAIL') == 'OnFailure' and not test_mode:
print('[INFO] Skipping sending of message for successful download')
sys.exit(POSTPROCESS_NONE)
if success:
subject = 'Success for "%s"' % (os.environ['NZBPP_NZBNAME'])
text = 'Download of "%s" has successfully completed.' % (os.environ['NZBPP_NZBNAME'])
subject = 'Success for "%s"' % (os.environ.get('NZBPP_NZBNAME', 'Test download'))
text = 'Download of "%s" has successfully completed.' % (os.environ.get('NZBPP_NZBNAME', 'Test download'))
else:
subject = 'Failure for "%s"' % (os.environ['NZBPP_NZBNAME'])
text = 'Download of "%s" has failed.' % (os.environ['NZBPP_NZBNAME'])
text += '\nStatus: %s' % status
if os.environ.get('NZBPO_STATISTICS') == 'yes' or \
if (os.environ.get('NZBPO_STATISTICS') == 'yes' or \
os.environ.get('NZBPO_NZBLOG') == 'Always' or \
(os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success):
(os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success)) and \
not test_mode:
# To get statistics or the post-processing log we connect to NZBGet via XML-RPC.
# For more info visit http://nzbget.net/RPC_API_reference
# For more info visit http://nzbget.net/api
# First we need to know connection info: host, port and password of NZBGet server.
# NZBGet passes all configuration options to post-processing script as
# environment variables.
@@ -162,7 +173,7 @@ if os.environ.get('NZBPO_STATISTICS') == 'yes' or \
# Create remote server object
server = ServerProxy(rpcUrl)
if os.environ.get('NZBPO_STATISTICS') == 'yes':
if os.environ.get('NZBPO_STATISTICS') == 'yes' and not test_mode:
# Find correct nzb in method listgroups
groups = server.listgroups(0)
nzbID = int(os.environ['NZBPP_NZBID'])
@@ -206,7 +217,7 @@ if os.environ.get('NZBPO_STATISTICS') == 'yes':
# add list of downloaded files
files = False
if os.environ.get('NZBPO_FILELIST') == 'yes':
if os.environ.get('NZBPO_FILELIST') == 'yes' and not test_mode:
text += '\n\nFiles:'
for dirname, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']):
for filename in filenames:
@@ -216,18 +227,19 @@ if os.environ.get('NZBPO_FILELIST') == 'yes':
text += '\n<no files found in the destination directory (moved by a script?)>'
# add _brokenlog.txt (if exists)
if os.environ.get('NZBPO_BROKENLOG') == 'yes':
if os.environ.get('NZBPO_BROKENLOG') == 'yes' and not test_mode:
brokenlog = '%s/_brokenlog.txt' % os.environ['NZBPP_DIRECTORY']
if os.path.exists(brokenlog):
text += '\n\nBrokenlog:\n' + open(brokenlog, 'r').read().strip()
# add post-processing log
if os.environ.get('NZBPO_NZBLOG') == 'Always' or \
(os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success):
if (os.environ.get('NZBPO_NZBLOG') == 'Always' or \
(os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success)) and \
not test_mode:
# To get the item log we connect to NZBGet via XML-RPC and call
# method "loadlog", which returns the log for a given nzb item.
# For more info visit http://nzbget.net/RPC_API_reference
# For more info visit http://nzbget.net/api
# Call remote method 'loadlog'
nzbid = int(os.environ['NZBPP_NZBID'])

View File

@@ -114,6 +114,8 @@ class Nzbget:
config.write('WarningTarget=log\n')
config.write('ErrorTarget=log\n')
config.write('DebugTarget=none\n')
config.write('CrashTrace=no\n')
config.write('CrashDump=yes\n')
config.write('ContinuePartial=no\n')
config.write('DirectWrite=yes\n')
config.write('ArticleCache=500\n')

View File

@@ -41,6 +41,11 @@ def prepare_testdata(request):
os.makedirs(nserv_datadir + '/small')
shutil.copyfile(nzbget_srcdir +'/COPYING', nserv_datadir + '/small/small.dat')
if not os.path.exists(nserv_datadir + '/small-obfuscated.nzb'):
if not os.path.exists(nserv_datadir + '/small-obfuscated'):
os.makedirs(nserv_datadir + '/small-obfuscated')
shutil.copyfile(nzbget_srcdir +'/COPYING', nserv_datadir + '/small-obfuscated/fsdkhKHGuwuMNBKskd')
if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '3000', '-q']):
pytest.exit('Test file generation failed')

View File

@@ -0,0 +1,28 @@
import os
nzbget_options = ['HealthCheck=none', 'ArticleCache=500', 'DirectWrite=yes', 'FileNaming=auto']
def test_small_obfuscated(nserv, nzbget):
hist = nzbget.download_nzb('small-obfuscated.nzb')
assert hist['Status'] == 'SUCCESS/HEALTH'
assert os.path.exists(hist['DestDir'] + '/fsdkhKHGuwuMNBKskd')
def test_small_obfuscated_bad(nserv, nzbget):
nzb_content = nzbget.load_nzb('small-obfuscated.nzb')
nzb_content = nzb_content.replace(';fsdkhKHGuwuMNBKskd', ';gpl.txt')
hist = nzbget.download_nzb('small-obfuscated.mod.nzb', nzb_content, params=[('*naming', 'nzb')])
assert hist['Status'] == 'SUCCESS/HEALTH'
assert not os.path.exists(hist['DestDir'] + '/fsdkhKHGuwuMNBKskd')
assert os.path.exists(hist['DestDir'] + '/gpl.txt')
def test_small(nserv, nzbget):
hist = nzbget.download_nzb('small.nzb')
assert hist['Status'] == 'SUCCESS/HEALTH'
assert os.path.exists(hist['DestDir'] + '/small.dat')
def test_small_changed(nserv, nzbget):
nzb_content = nzbget.load_nzb('small.nzb')
nzb_content = nzb_content.replace(';small.dat', ';small-changed.dat')
hist = nzbget.download_nzb('small-changed.nzb', nzb_content)
assert hist['Status'] == 'SUCCESS/HEALTH'
assert os.path.exists(hist['DestDir'] + '/small.dat')

View File

@@ -12,6 +12,7 @@ def prepare_testdata(request):
nserv_datadir = pytest.config.getini('nserv_datadir')
nzbget_bin = pytest.config.getini('nzbget_bin')
sevenzip_bin = pytest.config.getini('sevenzip_bin')
par2_bin = pytest.config.getini('par2_bin')
if not os.path.exists(par2_bin):
@@ -99,3 +100,75 @@ def prepare_testdata(request):
pytest.exit('Test file generation failed')
if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '500', '-q']):
pytest.exit('Test file generation failed')
if not os.path.exists(nserv_datadir + '/obfuscated1.nzb'):
create_test_file(nserv_datadir + '/obfuscated1', sevenzip_bin, 5, 1)
os.chdir(nserv_datadir + '/obfuscated1')
if 0 != subprocess.call([par2_bin, 'c', '-b100', 'parrename.par2', '*']):
pytest.exit('Test file generation failed')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.001', nserv_datadir + '/obfuscated1/abc.51')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.002', nserv_datadir + '/obfuscated1/abc.01')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.003', nserv_datadir + '/obfuscated1/abc.21')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.004', nserv_datadir + '/obfuscated1/abc.34')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.005', nserv_datadir + '/obfuscated1/abc.17')
os.rename(nserv_datadir + '/obfuscated1/5mb.7z.006', nserv_datadir + '/obfuscated1/abc.00')
if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '100000', '-q']):
pytest.exit('Test file generation failed')
if not os.path.exists(nserv_datadir + '/obfuscated2.nzb'):
create_test_file(nserv_datadir + '/obfuscated2', sevenzip_bin, 5, 1)
os.chdir(nserv_datadir + '/obfuscated2')
if 0 != subprocess.call([par2_bin, 'c', '-b100', 'parrename.par2', '*']):
pytest.exit('Test file generation failed')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.001', nserv_datadir + '/obfuscated2/abc.51')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.002', nserv_datadir + '/obfuscated2/abc.01')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.003', nserv_datadir + '/obfuscated2/abc.21')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.004', nserv_datadir + '/obfuscated2/abc.34')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.005', nserv_datadir + '/obfuscated2/abc.17')
os.rename(nserv_datadir + '/obfuscated2/5mb.7z.006', nserv_datadir + '/obfuscated2/abc.00')
os.rename(nserv_datadir + '/obfuscated2/parrename.par2', nserv_datadir + '/obfuscated2/abc.90')
os.rename(nserv_datadir + '/obfuscated2/parrename.vol0+1.par2', nserv_datadir + '/obfuscated2/abc.95')
os.rename(nserv_datadir + '/obfuscated2/parrename.vol1+2.par2', nserv_datadir + '/obfuscated2/abc.91')
os.rename(nserv_datadir + '/obfuscated2/parrename.vol3+2.par2', nserv_datadir + '/obfuscated2/abc.92')
if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '100000', '-q']):
pytest.exit('Test file generation failed')
if not os.path.exists(nserv_datadir + '/obfuscated3.nzb'):
create_test_file(nserv_datadir + '/obfuscated3', sevenzip_bin, 100, 10)
os.chdir(nserv_datadir + '/obfuscated3')
if 0 != subprocess.call([par2_bin, 'c', '-b100', 'parrename.par2', '*']):
pytest.exit('Test file generation failed')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.001', nserv_datadir + '/obfuscated3/abc.51')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.002', nserv_datadir + '/obfuscated3/abc.01')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.003', nserv_datadir + '/obfuscated3/abc.21')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.004', nserv_datadir + '/obfuscated3/abc.34')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.005', nserv_datadir + '/obfuscated3/abc.17')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.006', nserv_datadir + '/obfuscated3/abc.60')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.007', nserv_datadir + '/obfuscated3/abc.32')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.008', nserv_datadir + '/obfuscated3/abc.35')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.009', nserv_datadir + '/obfuscated3/abc.41')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.010', nserv_datadir + '/obfuscated3/abc.50')
os.rename(nserv_datadir + '/obfuscated3/100mb.7z.011', nserv_datadir + '/obfuscated3/abc.43')
os.rename(nserv_datadir + '/obfuscated3/parrename.par2', nserv_datadir + '/obfuscated3/abc.00')
os.rename(nserv_datadir + '/obfuscated3/parrename.vol0+1.par2', nserv_datadir + '/obfuscated3/abc.02')
os.rename(nserv_datadir + '/obfuscated3/parrename.vol1+2.par2', nserv_datadir + '/obfuscated3/abc.91')
os.rename(nserv_datadir + '/obfuscated3/parrename.vol3+2.par2', nserv_datadir + '/obfuscated3/abc.92')
if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '100000', '-q']):
pytest.exit('Test file generation failed')
def create_test_file(bigdir, sevenzip_bin, sizemb, partmb):
print('Preparing test file (' + str(sizemb) + 'MB)')
if not os.path.exists(bigdir):
os.makedirs(bigdir)
f = open(bigdir + '/' + str(sizemb) + 'mb.dat', 'wb')
for n in xrange(sizemb / partmb):
print('Writing block %i from %i' % (n + 1, sizemb / partmb))
f.write(os.urandom(partmb * 1024 * 1024))
f.close()
if 0 != subprocess.call([sevenzip_bin, 'a', bigdir + '/' + str(sizemb) + 'mb.7z', '-mx=0', '-v' + str(partmb) + 'm', bigdir + '/' + str(sizemb) + 'mb.dat']):
pytest.exit('Test file generation failed')
os.remove(bigdir + '/' + str(sizemb) + 'mb.dat')

View File

@@ -1,4 +1,4 @@
nzbget_options = ['ParRename=yes', 'RarRename=yes', 'UnpackIgnoreExt=.cbr']
nzbget_options = ['ParRename=yes', 'RarRename=yes', 'UnpackIgnoreExt=.cbr', 'DirectRename=no']
def test_parrename(nserv, nzbget):
hist = nzbget.download_nzb('parrename.nzb', unpack=True)
@@ -67,3 +67,20 @@ def test_rarrename_rar5encnam(nserv, nzbget):
def test_rarrename_rar3ignoreext(nserv, nzbget):
hist = nzbget.download_nzb('rar3ignoreext.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/HEALTH'
def test_rename_obf1(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated1.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1ch(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace(';5mb.7z', ';abc')
nzb_content = nzb_content.replace(';parrename', ';def')
nzb_content = nzb_content.replace('.par2&', '&')
hist = nzbget.download_nzb('obfuscated1-changed.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf2(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated2.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'

View File

@@ -0,0 +1,17 @@
nzbget_options = ['ParRename=no', 'RarRename=no', 'ParCheck=manual', 'DirectRename=no']
def test_rename_obf1(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated1.nzb', unpack=True)
assert hist['Status'] == 'WARNING/DAMAGED'
def test_rename_obf1ch(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace(';5mb.7z', ';abc')
nzb_content = nzb_content.replace(';parrename.par2', ';def')
nzb_content = nzb_content.replace('.par2&', '&')
hist = nzbget.download_nzb('obfuscated1-changed.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'WARNING/DAMAGED'
def test_rename_obf2(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated2.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/HEALTH'

View File

@@ -0,0 +1,83 @@
nzbget_options = ['ParRename=no', 'RarRename=no', 'ParCheck=auto', 'DirectRename=yes']
def test_rename_obf1(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated1.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1ch(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace(';5mb.7z', ';abc')
nzb_content = nzb_content.replace(';parrename', ';def')
nzb_content = nzb_content.replace('.par2&', '&')
hist = nzbget.download_nzb('obfuscated1-changed.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf2(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated2.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated3.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dm(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?4=300000:100000', 'abc.01?4=300000:100000!0')
hist = nzbget.download_nzb('obfuscated1-damaged.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmf(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?1=0:100000', 'abc.01?1=0:100000!0')
hist = nzbget.download_nzb('obfuscated1-damaged-first.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmf2(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?1=0:100000', 'abc.01?1=0:100000!0')
nzb_content = nzb_content.replace('abc.00?1=0:108', 'abc.00?1=0:108!0')
hist = nzbget.download_nzb('obfuscated1-damaged-first2.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmp(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('parrename.vol0+1.par2?1=0:57736', 'parrename.vol0+1.par2?1=0:57736!0')
hist = nzbget.download_nzb('obfuscated1-damaged-par.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dm(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?17=1600000:100000', 'abc.01?17=1600000:100000!0')
hist = nzbget.download_nzb('obfuscated3-damaged.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dmf(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?11=0:100000', 'abc.01?11=0:100000!0')
hist = nzbget.download_nzb('obfuscated3-damaged-first.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dmf2(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?11=0:100000', 'abc.01?11=0:100000!0')
nzb_content = nzb_content.replace('abc.00?1=0:4704', 'abc.00?1=0:4704!0')
hist = nzbget.download_nzb('obfuscated3-damaged-first2.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'WARNING/HEALTH'
def test_renameparchecker_healthy(nserv, nzbget):
hist = nzbget.download_nzb('parchecker.nzb')
assert hist['Status'] == 'SUCCESS/HEALTH'
def test_parchecker_repair(nserv, nzbget):
nzb_content = nzbget.load_nzb('parchecker.nzb')
nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
assert hist['Status'] == 'SUCCESS/PAR'
def test_parchecker_dmp(nserv, nzbget):
nzb_content = nzbget.load_nzb('parchecker.nzb')
nzb_content = nzb_content.replace('parchecker/testfile.par2?1=0:3000', 'parchecker/testfile.par2?1=0:3000!0')
hist = nzbget.download_nzb('parchecker.damagedpar.nzb', nzb_content)
assert hist['Status'] == 'SUCCESS/HEALTH'
for entry in nzbget.api.loadlog(hist['ID'], 0, 10000):
assert entry['Kind'] != 'ERROR', entry['Text']

View File

@@ -0,0 +1,83 @@
nzbget_options = ['ParRename=yes', 'RarRename=yes', 'ParCheck=auto', 'DirectRename=yes']
def test_rename_obf1(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated1.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1ch(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace(';5mb.7z', ';abc')
nzb_content = nzb_content.replace(';parrename', ';def')
nzb_content = nzb_content.replace('.par2&', '&')
hist = nzbget.download_nzb('obfuscated1-changed.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf2(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated2.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3(nserv, nzbget):
hist = nzbget.download_nzb('obfuscated3.nzb', unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dm(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?4=300000:100000', 'abc.01?4=300000:100000!0')
hist = nzbget.download_nzb('obfuscated1-damaged.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmf(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?1=0:100000', 'abc.01?1=0:100000!0')
hist = nzbget.download_nzb('obfuscated1-damaged-first.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmf2(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('abc.01?1=0:100000', 'abc.01?1=0:100000!0')
nzb_content = nzb_content.replace('abc.00?1=0:108', 'abc.00?1=0:108!0')
hist = nzbget.download_nzb('obfuscated1-damaged-first2.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf1dmp(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated1.nzb')
nzb_content = nzb_content.replace('parrename.vol0+1.par2?1=0:57736', 'parrename.vol0+1.par2?1=0:57736!0')
hist = nzbget.download_nzb('obfuscated1-damaged-par.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dm(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?17=1600000:100000', 'abc.01?17=1600000:100000!0')
hist = nzbget.download_nzb('obfuscated3-damaged.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dmf(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?11=0:100000', 'abc.01?11=0:100000!0')
hist = nzbget.download_nzb('obfuscated3-damaged-first.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_rename_obf3dmf2(nserv, nzbget):
nzb_content = nzbget.load_nzb('obfuscated3.nzb')
nzb_content = nzb_content.replace('abc.01?11=0:100000', 'abc.01?11=0:100000!0')
nzb_content = nzb_content.replace('abc.00?1=0:4704', 'abc.00?1=0:4704!0')
hist = nzbget.download_nzb('obfuscated3-damaged-first2.nzb', nzb_content, unpack=True)
assert hist['Status'] == 'SUCCESS/UNPACK'
def test_parchecker_healthy(nserv, nzbget):
hist = nzbget.download_nzb('parchecker.nzb')
assert hist['Status'] == 'SUCCESS/HEALTH'
def test_parchecker_repair(nserv, nzbget):
nzb_content = nzbget.load_nzb('parchecker.nzb')
nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
assert hist['Status'] == 'SUCCESS/PAR'
def test_parchecker_dmp(nserv, nzbget):
nzb_content = nzbget.load_nzb('parchecker.nzb')
nzb_content = nzb_content.replace('parchecker/testfile.par2?1=0:3000', 'parchecker/testfile.par2?1=0:3000!0')
hist = nzbget.download_nzb('parchecker.damagedpar.nzb', nzb_content)
assert hist['Status'] == 'SUCCESS/HEALTH'
for entry in nzbget.api.loadlog(hist['ID'], 0, 10000):
assert entry['Kind'] != 'ERROR', entry['Text']

View File

@@ -35,7 +35,7 @@ public:
protected:
virtual void AddNewsServer(int id, bool active, const char* name, const char* host,
int port, const char* user, const char* pass, bool joinGroup, bool tls,
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_newsServers++;

View File

@@ -26,7 +26,7 @@
void AddTestServer(ServerPool* pool, int id, bool active, int level, bool optional, int group, int connections)
{
pool->AddServer(std::make_unique<NewsServer>(id, active, nullptr, "", 119,
pool->AddServer(std::make_unique<NewsServer>(id, active, nullptr, "", 119, 0,
"", "", false, false, nullptr, connections, 0, level, group, optional));
}

View File

@@ -0,0 +1,143 @@
/*
* 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 "catch.h"
#include "Options.h"
#include "DirectUnpack.h"
#include "FileSystem.h"
#include "TestUtil.h"
class DirectUnpackDownloadQueueMock : public DownloadQueue
{
public:
DirectUnpackDownloadQueueMock() { Init(this); }
virtual bool EditEntry(int ID, EEditAction action, const char* args) { return false; };
virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode,
EEditAction action, const char* args) { return false; }
virtual void HistoryChanged() {}
virtual void Save() {};
};
TEST_CASE("Direct-unpack simple", "[Rar][DirectUnpack][Unrar][Slow][TestData]")
{
Options::CmdOptList cmdOpts;
cmdOpts.push_back("WriteLog=none");
cmdOpts.push_back("NzbLog=no");
Options options(&cmdOpts, nullptr);
DirectUnpackDownloadQueueMock downloadQueue;
TestUtil::PrepareWorkingDir("empty");
INFO("This test requires working unrar 5 in search path");
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part01.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part02.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part03.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part03.rar").c_str()));
std::unique_ptr<NzbInfo> nzbInfo = std::make_unique<NzbInfo>();
NzbInfo* nzbPtr = nzbInfo.get();
nzbInfo->SetName("test");
nzbInfo->SetDestDir(TestUtil::WorkingDir().c_str());
downloadQueue.GetQueue()->Add(std::move(nzbInfo), false);
DirectUnpack::StartJob(nzbPtr);
while (true)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (nzbPtr->GetUnpackThread())
{
((DirectUnpack*)nzbPtr->GetUnpackThread())->NzbDownloaded(downloadQueue, nzbPtr);
break;
}
usleep(50*1000);
}
while (nzbPtr->GetDirectUnpackStatus() == NzbInfo::nsRunning)
{
usleep(20 * 1000);
}
REQUIRE(nzbPtr->GetDirectUnpackStatus() == NzbInfo::nsSuccess);
REQUIRE(FileSystem::FileExists((TestUtil::WorkingDir() + "/_unpack/testfile3.dat").c_str()));
}
TEST_CASE("Direct-unpack two archives", "[Rar][DirectUnpack][Unrar][Slow][TestData]")
{
Options::CmdOptList cmdOpts;
cmdOpts.push_back("WriteLog=none");
cmdOpts.push_back("NzbLog=no");
Options options(&cmdOpts, nullptr);
DirectUnpackDownloadQueueMock downloadQueue;
TestUtil::PrepareWorkingDir("empty");
INFO("This test requires working unrar 5 in search path");
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part01.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part02.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part03.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile3.part03.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part01.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile5.part01.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part02.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile5.part02.rar").c_str()));
REQUIRE(FileSystem::CopyFile((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part03.rar").c_str(),
(TestUtil::WorkingDir() + "/testfile5.part03.rar").c_str()));
std::unique_ptr<NzbInfo> nzbInfo = std::make_unique<NzbInfo>();
NzbInfo* nzbPtr = nzbInfo.get();
nzbInfo->SetName("test");
nzbInfo->SetDestDir(TestUtil::WorkingDir().c_str());
downloadQueue.GetQueue()->Add(std::move(nzbInfo), false);
DirectUnpack::StartJob(nzbPtr);
while (true)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
if (nzbPtr->GetUnpackThread())
{
((DirectUnpack*)nzbPtr->GetUnpackThread())->NzbDownloaded(downloadQueue, nzbPtr);
break;
}
usleep(50 * 1000);
}
while (nzbPtr->GetDirectUnpackStatus() == NzbInfo::nsRunning)
{
usleep(20 * 1000);
}
REQUIRE(nzbPtr->GetDirectUnpackStatus() == NzbInfo::nsSuccess);
REQUIRE(FileSystem::FileExists((TestUtil::WorkingDir() + "/_unpack/testfile3.dat").c_str()));
REQUIRE(FileSystem::FileExists((TestUtil::WorkingDir() + "/_unpack/testfile5.dat").c_str()));
}

View File

@@ -27,7 +27,7 @@
#include "FileSystem.h"
#include "TestUtil.h"
TEST_CASE("Dupe matcher", "[Par][DupeMatcher][Slow][TestData]")
TEST_CASE("Dupe matcher", "[Par][DupeMatcher][Unrar][Slow][TestData]")
{
Options options(nullptr, nullptr);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -245,7 +245,7 @@ var Options = (new function($)
description += '\n';
}
}
else if (line.indexOf('=') > -1)
else if (line.indexOf('=') > -1 || line.indexOf('@') > -1)
{
if (!section)
{
@@ -260,9 +260,16 @@ var Options = (new function($)
var option = {};
var enabled = line.substr(0, 1) !== '#';
option.caption = line.substr(enabled ? 0 : 1, line.indexOf('=') - (enabled ? 0 : 1)).trim();
var command = line.indexOf('=') === -1 && line.indexOf('@') > -1;
option.caption = line.substr(enabled ? 0 : 1, line.indexOf(command ? '@' : '=') - (enabled ? 0 : 1)).trim();
if (command)
{
var optpos = option.caption.indexOf('[');
option.commandopts = optpos > -1 ? option.caption.substring(optpos + 1, option.caption.indexOf(']')).toLowerCase() : 'settings';
option.caption = optpos > -1 ? option.caption.substring(0, optpos) : option.caption;
}
option.name = (nameprefix != '' ? nameprefix : '') + option.caption;
option.defvalue = line.substr(line.indexOf('=') + 1, 1000).trim();
option.defvalue = line.substr(line.indexOf(command ? '@' : '=') + 1, 1000).trim();
option.value = null;
option.sectionId = section.id;
option.select = [];
@@ -404,7 +411,7 @@ var Options = (new function($)
var option = section.options[j];
option.value = null;
var val = findOption(values, option.name);
if (val)
if (val && !option.commandopts)
{
option.value = val.Value;
}
@@ -773,7 +780,7 @@ var Config = (new function($)
'<label class="control-label">' +
'<a class="option-name" href="#" data-optid="' + option.formId + '" '+
'onclick="Config.scrollToOption(event, this)">' + caption + '</a>' +
(option.value === null && !section.postparam ?
(option.value === null && !section.postparam && !option.commandopts ?
' <a data-toggle="modal" href="#ConfigNewOptionHelp" class="label label-info">new</a>' : '') + '</label>'+
'<div class="controls">';
@@ -845,6 +852,13 @@ var Config = (new function($)
html += '<button type="button" id="' + option.formId + '_Editor" class="btn" onclick="' + option.editor.click + '($(\'input\', $(this).closest(\'table\')).attr(\'id\'))">' + option.editor.caption + '</button>';
html += '</td></tr></table>';
}
else if (option.commandopts)
{
option.type = 'command';
html += '<button type="button" id="' + option.formId + '" class="btn ' +
(option.commandopts.indexOf('danger') > -1 ? 'btn-danger' : 'btn-inverse') +
'" onclick="Config.commandClick(this)">' + value + '</button>';
}
else
{
option.type = 'text';
@@ -933,6 +947,8 @@ var Config = (new function($)
{
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.testConnection(this, \'' + setname + '\',\'' + section.id + '\')">Test Connection</button>';
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.serverStats(this, \'' + setname + '\',\'' + section.id + '\')">Volume Statistics</button>';
}
html += '<hr>';
html += '</div>';
@@ -1484,6 +1500,31 @@ var Config = (new function($)
Util.show('#' + btnId, command === 'Script');
}
this.commandClick = function(button)
{
var optFormId = $(button).attr('id');
var option = findOptionById(optFormId);
var script = option.name.substr(0, option.name.indexOf(':'));
var command = option.name.substr(option.name.indexOf(':') + 1, 1000);
var changedOptions = prepareSaveRequest(true, false, true);
function execScript()
{
ExecScriptDialog.showModal(script, command, 'SETTINGS', changedOptions);
}
if (option.commandopts.indexOf('danger') > -1)
{
$('#DangerScriptConfirmDialog_OK').text(option.defvalue);
$('#DangerScriptConfirmDialog_Command').text(command);
ConfirmDialog.showModal('DangerScriptConfirmDialog', execScript);
}
else
{
execScript();
}
}
/*** RSS FEEDS ********************************************************************/
this.editFilter = function(optFormId)
@@ -1572,6 +1613,58 @@ var Config = (new function($)
});
}
/*** DOWNLOADED VOLUMES FOR A SERVER *******************************************************/
this.serverStats = function(control, setname, sectionId)
{
var multiid = parseInt($(control).attr('data-multiid'));
// Because the settings page isn't saved yet and user can reorder servers
// we cannot just use the server-id in teh call to "StatDialog".
// Instead we need to find the server in loaded options and get its ID from there.
var serverName = getOptionValue(findOptionByName('Server' + multiid + '.Name'));
var serverHost = getOptionValue(findOptionByName('Server' + multiid + '.Host'));
var serverPort = getOptionValue(findOptionByName('Server' + multiid + '.Port'));
console.log(serverName, serverHost, serverPort);
var serverId = 0;
// searching by name
var opt = undefined;
for (var i = 1; opt !== null; i++)
{
var opt = Options.option('Server' + i + '.Name');
if (opt === serverName)
{
serverId = i;
break;
}
}
if (serverId === 0)
{
// searching by host:port
var host = undefined;
for (var i = 1; host !== null; i++)
{
host = Options.option('Server' + i + '.Host');
var port = Options.option('Server' + i + '.Port');
if (host === serverHost && port === serverPort)
{
serverId = i;
break;
}
}
}
if (serverId === 0)
{
AlertDialog.showModal('Downloaded volumes', 'No statistics available for that server yet.');
return;
}
StatDialog.showModal(serverId);
}
/*** SAVE ********************************************************************/
function getOptionValue(option)
@@ -1620,7 +1713,7 @@ var Config = (new function($)
return false;
}
function prepareSaveRequest(onlyUserChanges, webSettings)
function prepareSaveRequest(onlyUserChanges, webSettings, onlyChangedOptions)
{
var modified = false;
var request = [];
@@ -1635,7 +1728,7 @@ var Config = (new function($)
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template && !(option.type === 'info'))
if (!option.template && !(option.type === 'info') && !option.commandopts)
{
var oldValue = option.value;
var newValue = getOptionValue(option);
@@ -1647,14 +1740,18 @@ var Config = (new function($)
{
if (onlyUserChanges)
{
modified = modified || (oldValue != newValue && oldValue !== null);
var optmodified = oldValue != newValue && oldValue !== null;
}
else
{
modified = modified || (oldValue != newValue) || (option.value === null);
var optmodified = (oldValue != newValue) || (option.value === null);
}
modified = modified || optmodified;
if (optmodified || !onlyChangedOptions)
{
var opt = {Name: option.name, Value: newValue};
request.push(opt);
}
var opt = {Name: option.name, Value: newValue};
request.push(opt);
}
}
modified = modified || section.modified;
@@ -2924,3 +3021,101 @@ var UpdateDialog = (new function($)
}
}(jQuery));
/*** EXEC SCRIPT DIALOG *******************************************************/
var ExecScriptDialog = (new function($)
{
'use strict'
// Controls
var $ExecScriptDialog;
var $ExecScriptDialog_Log;
var $ExecScriptDialog_Title;
var $ExecScriptDialog_Status;
// State
var logReceived = false;
var visible = false;
this.init = function()
{
$ExecScriptDialog = $('#ExecScriptDialog');
$ExecScriptDialog_Log = $('#ExecScriptDialog_Log');
$ExecScriptDialog_Title = $('#ExecScriptDialog_Title');
$ExecScriptDialog_Status = $('#ExecScriptDialog_Status');
$ExecScriptDialog.on('hidden', function() { visible = false; });
}
this.showModal = function(script, command, context, changedOptions)
{
$ExecScriptDialog_Title.text('Executing script ' + script);
$ExecScriptDialog_Log.text('');
$ExecScriptDialog_Status.show();
$ExecScriptDialog.modal({backdrop: 'static'});
visible = true;
RPC.call('startscript', [script, command, context, changedOptions],
function (result)
{
if (result)
{
updateLog();
}
else
{
setLogContentAndScroll('<span class="script-log-error">Script start failed</span>');
$ExecScriptDialog_Status.hide();
}
}
);
}
function updateLog()
{
RPC.call('logscript', [0, 100], function(data)
{
updateLogTable(data);
if (visible)
{
setTimeout(updateLog, 500);
}
});
}
function setLogContentAndScroll(html)
{
var scroll = $ExecScriptDialog_Log.prop('scrollHeight') - $ExecScriptDialog_Log.prop('scrollTop') === $ExecScriptDialog_Log.prop('clientHeight');
$ExecScriptDialog_Log.html(html);
if (scroll)
{
$ExecScriptDialog_Log.scrollTop($ExecScriptDialog_Log.prop('scrollHeight'));
}
}
function updateLogTable(messages)
{
var html = '';
for (var i=0; i < messages.length; i++)
{
var message = messages[i];
var text = Util.textToHtml(message.Text);
if (text.substr(0, 7) === 'Script ')
{
$ExecScriptDialog_Status.fadeOut(500);
if (message.Kind === 'INFO')
{
text = '<span class="script-log-success">' + text + '</span>';
}
}
if (message.Kind === 'ERROR')
{
text = '<span class="script-log-error">' + text + '</span>';
}
html = html + text + '\n';
}
setLogContentAndScroll(html);
}
}(jQuery));

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -20,7 +20,8 @@
/*
* In this module:
* 1) Download tab;
* 2) Functions for html generation for downloads, also used from other modules (edit and add dialogs).
* 2) Functions for html generation for downloads, also used from other modules (edit and add dialogs);
* 3) Popup menus in downloads list.
*/
/*** DOWNLOADS TAB ***********************************************************/
@@ -36,6 +37,8 @@ var Downloads = (new function($)
var $DownloadQueueEmpty;
var $DownloadsRecordsPerPage;
var $DownloadsTable_Name;
var $PriorityMenu;
var $CategoryMenu;
// State
var notification = null;
@@ -43,7 +46,6 @@ var Downloads = (new function($)
var groups;
var urls;
var nameColumnWidth = null;
var minLevel = null;
var statusData = {
'QUEUED': { Text: 'QUEUED', PostProcess: false },
@@ -75,6 +77,8 @@ var Downloads = (new function($)
$DownloadQueueEmpty = $('#DownloadQueueEmpty');
$DownloadsRecordsPerPage = $('#DownloadsRecordsPerPage');
$DownloadsTable_Name = $('#DownloadsTable_Name');
$PriorityMenu = $('#PriorityMenu');
$CategoryMenu = $('#DownloadsCategoryMenu');
var recordsPerPage = UISettings.read('DownloadsRecordsPerPage', 10);
$DownloadsRecordsPerPage.val(recordsPerPage);
@@ -101,6 +105,13 @@ var Downloads = (new function($)
});
$DownloadsTable.on('click', 'a', itemClick);
$DownloadsTable.on('click', 'td:nth-child(2).dropdown-cell > div:not(.dropdown-disabled)', priorityClick);
$DownloadsTable.on('click', 'td:nth-child(3).dropdown-cell > div', statusClick);
$DownloadsTable.on('click', 'td:nth-child(5).dropdown-cell > div:not(.dropdown-disabled)', categoryClick);
$PriorityMenu.on('click', 'a', priorityMenuClick);
$CategoryMenu.on('click', 'a', categoryMenuClick);
DownloadsActionsMenu.init();
}
this.applyTheme = function()
@@ -205,7 +216,7 @@ var Downloads = (new function($)
var group = item.data;
var status = DownloadsUI.buildStatus(group);
var priority = DownloadsUI.buildPriority(group.MaxPriority);
var priority = DownloadsUI.buildPriority(group);
var progresslabel = DownloadsUI.buildProgressLabel(group, nameColumnWidth);
var progress = DownloadsUI.buildProgress(group, item.data.size, item.data.left, item.data.estimated);
var dupe = DownloadsUI.buildDupe(group.DupeKey, group.DupeScore, group.DupeMode);
@@ -235,18 +246,14 @@ var Downloads = (new function($)
'">health: ' + Math.floor(group.Health / 10) + '%</span> ';
}
var backup = '';
var backupPercent = calcBackupPercent(group);
if (backupPercent > 0)
{
backup = ' <a href="#" data-nzbid="' + group.NZBID + '" data-area="backup" class="badge-link"><span class="label label-warning" title="using backup news servers">backup: ' +
(backupPercent < 10 ? Util.round1(backupPercent) : Util.round0(backupPercent)) + '%</span> ';
}
var category = Util.textToHtml(group.Category);
var category = group.Category !== '' ? Util.textToHtml(group.Category) : '<span class="none-category">None</span>';
var backup = DownloadsUI.buildBackupLabel(group);
if (!UISettings.miniTheme)
{
priority = '<div data-nzbid="' + group.NZBID + '"' + (group.postprocess ? ' class="dropdown-disabled"' : '') + '>' + priority + '</div>';
status = '<div data-nzbid="' + group.NZBID + '">' + status + '</div>';
category = '<div data-nzbid="' + group.NZBID + '"' + (group.postprocess ? ' class="dropdown-disabled"' : '') + '>' + category + '</div>';
var info = name + ' ' + url + dupe + health + backup + propagation + progresslabel;
item.fields = ['<div class="check img-check"></div>', priority, status, info, category, item.data.age, progress, item.data.estimated];
}
@@ -255,7 +262,7 @@ var Downloads = (new function($)
var info = '<div class="check img-check"></div><span class="row-title">' +
name + '</span>' + url + ' ' + (group.MaxPriority == 0 ? '' : priority) +
' ' + (group.Status === 'QUEUED' ? '' : status) + dupe + health + backup + propagation;
if (category)
if (group.Category !== '')
{
info += ' <span class="label label-status">' + category + '</span>';
}
@@ -270,56 +277,28 @@ var Downloads = (new function($)
function renderCellCallback(cell, index, item)
{
if (index == 1)
if (index === 1)
{
cell.className = 'text-center';
cell.className = 'priority-cell' + (!UISettings.miniTheme ? ' dropdown-cell' : '');
}
else if (5 <= index && index <= 8)
else if (index === 2 || index === 4)
{
cell.className = !UISettings.miniTheme ? 'dropdown-cell dropafter-cell' : '';
}
else if (index === 3)
{
cell.className = !UISettings.miniTheme ? 'dropafter-cell' : '';
}
else if (index === 5)
{
cell.className = 'text-right' + (!UISettings.miniTheme ? ' dropafter-cell' : '');
}
else if (6 <= index && index <= 8)
{
cell.className = 'text-right';
}
}
function calcBackupPercent(group)
{
var downloadedArticles = group.SuccessArticles + group.FailedArticles;
if (downloadedArticles === 0)
{
return 0;
}
if (minLevel === null)
{
for (var i=0; i < Status.status.NewsServers.length; i++)
{
var server = Status.status.NewsServers[i];
var level = parseInt(Options.option('Server' + server.ID + '.Level'));
if (minLevel === null || minLevel > level)
{
minLevel = level;
}
}
}
var backupArticles = 0;
for (var j=0; j < group.ServerStats.length; j++)
{
var stat = group.ServerStats[j];
var level = parseInt(Options.option('Server' + stat.ServerID + '.Level'));
if (level > minLevel && stat.SuccessArticles > 0)
{
backupArticles += stat.SuccessArticles;
}
}
var backupPercent = 0;
if (backupArticles > 0)
{
backupPercent = backupArticles * 100.0 / downloadedArticles;
}
return backupPercent;
}
this.recordsPerPageChange = function()
{
var val = $DownloadsRecordsPerPage.val();
@@ -369,6 +348,19 @@ var Downloads = (new function($)
/*** EDIT ******************************************************/
function findGroup(nzbid)
{
for (var i=0; i<groups.length; i++)
{
var gr = groups[i];
if (gr.NZBID == nzbid)
{
return gr;
}
}
return null;
}
function itemClick(e)
{
e.preventDefault();
@@ -426,10 +418,16 @@ var Downloads = (new function($)
return checkedEditIDs;
}
this.buildEditIDList = function()
function buildContextIdList(group)
{
return checkBuildEditIDList(true, true, true);
var editIds = checkBuildEditIDList(true, true, true);
if (editIds.indexOf(group.NZBID) === -1)
{
editIds = [group.NZBID];
}
return editIds;
}
this.buildContextIdList = buildContextIdList;
/*** TOOLBAR: SELECTED ITEMS ******************************************************/
@@ -613,6 +611,75 @@ var Downloads = (new function($)
}
}
function priorityClick(e)
{
e.preventDefault();
e.stopPropagation();
var group = findGroup($(this).attr('data-nzbid'));
var editIds = buildContextIdList(group);
$PriorityMenu.data('nzbids', editIds);
DownloadsUI.updateContextWarning($PriorityMenu, editIds);
$('i', $PriorityMenu).removeClass('icon-ok').addClass('icon-empty');
$('li[data=' + group.MaxPriority + '] i', $PriorityMenu).addClass('icon-ok');
Frontend.showPopupMenu($PriorityMenu, 'left',
{ left: $(this).offset().left - 30, top: $(this).offset().top,
width: $(this).width() + 30, height: $(this).outerHeight() - 2});
}
function priorityMenuClick(e)
{
e.preventDefault();
var priority = $(this).parent().attr('data');
var nzbids = $PriorityMenu.data('nzbids');
notification = '#Notif_Downloads_Changed';
RPC.call('editqueue', ['GroupSetPriority', '' + priority, nzbids], editCompleted);
}
function statusClick(e)
{
e.preventDefault();
e.stopPropagation();
var group = findGroup($(this).attr('data-nzbid'));
DownloadsActionsMenu.showPopupMenu(group, 'left',
{ left: $(this).offset().left - 30, top: $(this).offset().top,
width: $(this).width() + 30, height: $(this).outerHeight() - 2 },
function(_notification) { notification = _notification; },
editCompleted);
}
function categoryClick(e)
{
e.preventDefault();
e.stopPropagation();
DownloadsUI.fillCategoryMenu($CategoryMenu);
var group = findGroup($(this).attr('data-nzbid'));
if (group.postprocess)
{
return;
}
var editIds = buildContextIdList(group);
$CategoryMenu.data('nzbids', editIds);
DownloadsUI.updateContextWarning($CategoryMenu, editIds);
$('i', $CategoryMenu).removeClass('icon-ok').addClass('icon-empty');
$('li[data="' + group.Category + '"] i', $CategoryMenu).addClass('icon-ok');
Frontend.showPopupMenu($CategoryMenu, 'left',
{ left: $(this).offset().left - 30, top: $(this).offset().top,
width: $(this).width() + 30, height: $(this).outerHeight() - 2 });
}
function categoryMenuClick(e)
{
e.preventDefault();
var category = $(this).parent().attr('data');
var nzbids = $CategoryMenu.data('nzbids');
notification = '#Notif_Downloads_Changed';
RPC.call('editqueue', ['GroupApplyCategory', category, nzbids], editCompleted);
}
}(jQuery));
@@ -625,6 +692,7 @@ var DownloadsUI = (new function($)
// State
var categoryColumnWidth = null;
var dupeCheck = null;
var minLevel = null;
this.fillPriorityCombo = function(combo)
{
@@ -648,6 +716,23 @@ var DownloadsUI = (new function($)
}
}
this.fillCategoryMenu = function(menu)
{
if (menu.data('initialized'))
{
return;
}
var templ = $('li:last-child', menu);
for (var i=0; i < Options.categories.length; i++)
{
var item = templ.clone().show();
$('td:last-child', item).text(Options.categories[i]);
item.attr('data', Options.categories[i]);
menu.append(item);
}
menu.data('initialized', true);
}
this.buildStatusText = function(group)
{
var statusText = Downloads.statusData[group.Status].Text;
@@ -799,8 +884,9 @@ var DownloadsUI = (new function($)
}
}
this.buildPriority = function(priority)
this.buildPriority = function(group)
{
var priority = group.MaxPriority
var text;
if (priority >= 900) text = ' <div class="icon-circle-red" title="Force priority"></div>';
@@ -834,6 +920,58 @@ var DownloadsUI = (new function($)
' <span class="label label-info" title="'+ Util.textToAttr(encryptedPassword) +'">encrypted</span>' : '';
}
this.buildBackupLabel = function(group)
{
var backup = '';
var backupPercent = calcBackupPercent(group);
if (backupPercent > 0)
{
backup = ' <a href="#" data-nzbid="' + group.NZBID + '" data-area="backup" class="badge-link"><span class="label label-warning" title="with backup news servers">backup: ' +
(backupPercent < 10 ? Util.round1(backupPercent) : Util.round0(backupPercent)) + '%</span> ';
}
return backup;
}
function calcBackupPercent(group)
{
var downloadedArticles = group.SuccessArticles + group.FailedArticles;
if (downloadedArticles === 0)
{
return 0;
}
if (minLevel === null)
{
for (var i=0; i < Status.status.NewsServers.length; i++)
{
var server = Status.status.NewsServers[i];
var level = parseInt(Options.option('Server' + server.ID + '.Level'));
if (minLevel === null || minLevel > level)
{
minLevel = level;
}
}
}
var backupArticles = 0;
for (var j=0; j < group.ServerStats.length; j++)
{
var stat = group.ServerStats[j];
var level = parseInt(Options.option('Server' + stat.ServerID + '.Level'));
if (level > minLevel && stat.SuccessArticles > 0)
{
backupArticles += stat.SuccessArticles;
}
}
var backupPercent = 0;
if (backupArticles > 0)
{
backupPercent = backupArticles * 100.0 / downloadedArticles;
}
return backupPercent;
}
function formatDupeText(dupeKey, dupeScore, dupeMode)
{
dupeKey = dupeKey.replace('rageid=', '');
@@ -912,12 +1050,36 @@ var DownloadsUI = (new function($)
widthHelper.remove();
categoryColumnWidth += 'px';
categoryColumnWidth = (categoryColumnWidth + 8) + 'px';
}
return categoryColumnWidth;
}
this.buildDNZBLinks = function(parameters, prefix)
{
$('.' + prefix).hide();
var hasItems = false;
for (var i=0; i < parameters.length; i++)
{
var param = parameters[i];
if (param.Name.substr(0, 6) === '*DNZB:')
{
var linkName = param.Name.substr(6, 100);
var $paramLink = $('#' + prefix + '_' + linkName);
if($paramLink.length > 0)
{
$paramLink.attr('href', param.Value);
$paramLink.show();
hasItems = true;
}
}
}
Util.show('#' + prefix + '_Section', hasItems);
}
this.deleteConfirm = function(actionCallback, multi, hasNzb, hasUrl, selCount)
{
var dupeCheck = Options.option('DupeCheck') === 'yes';
@@ -957,4 +1119,105 @@ var DownloadsUI = (new function($)
ConfirmDialog.showModal('DownloadsDeleteConfirmDialog', action, init, selCount);
}
this.updateContextWarning = function(menu, editIds)
{
var warning = $('.dropdown-warning', $(menu));
Util.show(warning, editIds.length > 1);
warning.text(editIds.length + ' records selected');
}
}(jQuery));
/*** DOWNLOADS ACTION MENU *************************************************************************/
var DownloadsActionsMenu = (new function()
{
'use strict'
var $ActionsMenu;
var curGroup;
var beforeCallback;
var completedCallback;
var editIds;
this.init = function()
{
$ActionsMenu = $('#DownloadsActionsMenu');
$('#DownloadsActions_Pause').click(itemPause);
$('#DownloadsActions_Resume').click(itemResume);
$('#DownloadsActions_Delete').click(itemDelete);
$('#DownloadsActions_CancelPP').click(itemCancelPP);
}
this.showPopupMenu = function(group, anchor, rect, before, completed)
{
curGroup = group;
beforeCallback = before;
completedCallback = completed;
editIds = Downloads.buildContextIdList(group);
// setup menu items
Util.show('#DownloadsActions_CancelPP', group.postprocess);
Util.show('#DownloadsActions_Delete', !group.postprocess);
Util.show('#DownloadsActions_Pause', group.Kind === 'NZB' && !group.postprocess);
Util.show('#DownloadsActions_Resume', false);
DownloadsUI.updateContextWarning($ActionsMenu, editIds);
if (!group.postprocess &&
(group.RemainingSizeHi == group.PausedSizeHi &&
group.RemainingSizeLo == group.PausedSizeLo &&
group.Kind === 'NZB'))
{
$('#DownloadsActions_Resume').show();
$('#DownloadsActions_Pause').hide();
}
DownloadsUI.buildDNZBLinks(group.Parameters, 'DownloadsActions_DNZB');
Frontend.showPopupMenu($ActionsMenu, anchor, rect);
}
function itemPause(e)
{
e.preventDefault();
beforeCallback('#Notif_Downloads_Paused');
RPC.call('editqueue', ['GroupPause', '', editIds], completedCallback);
}
function itemResume(e)
{
e.preventDefault();
beforeCallback('#Notif_Downloads_Resumed');
RPC.call('editqueue', ['GroupResume', '', editIds], function()
{
if (Options.option('ParCheck') === 'force')
{
completedCallback();
}
else
{
RPC.call('editqueue', ['GroupPauseExtraPars', '', editIds], completedCallback);
}
});
}
function itemDelete(e)
{
e.preventDefault();
DownloadsUI.deleteConfirm(doItemDelete, false, curGroup.Kind === 'NZB', curGroup.Kind === 'URL');
}
function doItemDelete(command)
{
beforeCallback('#Notif_Downloads_Deleted');
RPC.call('editqueue', [command, '', editIds], completedCallback);
}
function itemCancelPP(e)
{
e.preventDefault();
beforeCallback('#Notif_Downloads_PostCanceled');
RPC.call('editqueue', ['PostDelete', '', editIds], completedCallback);
}
}(jQuery));

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -56,10 +56,7 @@ var DownloadsEditDialog = (new function($)
$DownloadsEdit_ParamData = $('#DownloadsEdit_ParamData');
$('#DownloadsEdit_Save').click(saveChanges);
$('#DownloadsEdit_Pause').click(itemPause);
$('#DownloadsEdit_Resume').click(itemResume);
$('#DownloadsEdit_Delete').click(itemDelete);
$('#DownloadsEdit_CancelPP').click(itemCancelPP);
$('#DownloadsEdit_Actions').click(itemActions);
$('#DownloadsEdit_Param, #DownloadsEdit_Log, #DownloadsEdit_File, #DownloadsEdit_Dupe').click(tabClick);
$('#DownloadsEdit_Back').click(backClick);
$('#DownloadsEdit_Category').change(categoryChange);
@@ -205,10 +202,6 @@ var DownloadsEditDialog = (new function($)
var postParamConfig = ParamTab.createPostParamConfig();
Util.show('#DownloadsEdit_NZBNameReadonly', group.postprocess);
Util.show('#DownloadsEdit_CancelPP', group.postprocess);
Util.show('#DownloadsEdit_Delete', !group.postprocess);
Util.show('#DownloadsEdit_Pause', group.Kind === 'NZB' && !group.postprocess);
Util.show('#DownloadsEdit_Resume', false);
Util.show('#DownloadsEdit_Save', !group.postprocess);
Util.show('#DownloadsEdit_StatisticsGroup', group.Kind === 'NZB');
Util.show('#DownloadsEdit_File', group.Kind === 'NZB');
@@ -236,13 +229,6 @@ var DownloadsEditDialog = (new function($)
$('#DownloadsEdit_Category').removeAttr('disabled');
$('#DownloadsEdit_Close').removeClass('btn-primary');
$('#DownloadsEdit_Close').text('Cancel');
if (group.RemainingSizeHi == group.PausedSizeHi && group.RemainingSizeLo == group.PausedSizeLo &&
group.Kind === 'NZB')
{
$('#DownloadsEdit_Resume').show();
$('#DownloadsEdit_Pause').hide();
}
}
if (postParam)
@@ -250,8 +236,6 @@ var DownloadsEditDialog = (new function($)
postParams = ParamTab.buildPostParamTab($DownloadsEdit_ParamData, postParamConfig, curGroup.Parameters);
}
EditUI.buildDNZBLinks(curGroup.Parameters, 'DownloadsEdit_DNZB');
enableAllButtons();
$('#DownloadsEdit_GeneralTab').show();
@@ -429,51 +413,21 @@ var DownloadsEditDialog = (new function($)
: saveDupeKey();
}
function itemPause(e)
function itemActions(e)
{
e.preventDefault();
disableAllButtons();
notification = '#Notif_Downloads_Paused';
RPC.call('editqueue', ['GroupPause', '', [curGroup.NZBID]], completed);
}
e.stopPropagation();
var elem = $('#DownloadsEdit_Actions').parent();
function itemResume(e)
{
e.preventDefault();
disableAllButtons();
notification = '#Notif_Downloads_Resumed';
RPC.call('editqueue', ['GroupResume', '', [curGroup.NZBID]], function()
{
if (Options.option('ParCheck') === 'force')
DownloadsActionsMenu.showPopupMenu(curGroup, 'top',
{ left: elem.offset().left, top: elem.offset().top - 1,
width: elem.width(), height: elem.height() + 2 },
function(_notification)
{
completed();
}
else
{
RPC.call('editqueue', ['GroupPauseExtraPars', '', [curGroup.NZBID]], completed);
}
});
}
function itemDelete(e)
{
e.preventDefault();
DownloadsUI.deleteConfirm(doItemDelete, false, curGroup.Kind === 'NZB', curGroup.Kind === 'URL');
}
function doItemDelete(command)
{
disableAllButtons();
notification = '#Notif_Downloads_Deleted';
RPC.call('editqueue', [command, '', [curGroup.NZBID]], completed);
}
function itemCancelPP(e)
{
e.preventDefault();
disableAllButtons();
notification = '#Notif_Downloads_PostCanceled';
RPC.call('editqueue', ['PostDelete', '', [curGroup.NZBID]], completed);
disableAllButtons();
notification = _notification;
},
completed);
}
function categoryChange()
@@ -857,30 +811,6 @@ var EditUI = (new function($)
{
'use strict'
this.buildDNZBLinks = function(parameters, prefix)
{
$('.' + prefix).hide();
var hasItems = false;
for (var i=0; i < parameters.length; i++)
{
var param = parameters[i];
if (param.Name.substr(0, 6) === '*DNZB:')
{
var linkName = param.Name.substr(6, 100);
var $paramLink = $('#' + prefix + '_' + linkName);
if($paramLink.length > 0)
{
$paramLink.attr('href', param.Value);
$paramLink.show();
hasItems = true;
}
}
}
Util.show('#' + prefix + '_Section', hasItems);
}
/*** TAB: SERVER STATISTICS **************************************************/
this.fillServStats = function(table, editItem)
@@ -1062,7 +992,6 @@ var ParamTab = (new function($)
var option = section.options[j];
if (!option.template && !section.hidden && option.name.substr(option.name.length - 1, 1) === ':')
{
console.log(option.name);
var scriptName = option.name.substr(0, option.name.length-1);
if (oldScriptList.indexOf(scriptName) > -1 && newScriptList.indexOf(scriptName) === -1)
{
@@ -1556,6 +1485,7 @@ var HistoryEditDialog = (new function()
var lastFullscreen;
var saveCompleted;
var logFilled;
var showing;
this.init = function()
{
@@ -1563,16 +1493,9 @@ var HistoryEditDialog = (new function()
$HistoryEdit_ParamData = $('#HistoryEdit_ParamData');
$('#HistoryEdit_Save').click(saveChanges);
$('#HistoryEdit_Delete').click(itemDelete);
$('#HistoryEdit_Return, #HistoryEdit_ReturnURL').click(itemReturn);
$('#HistoryEdit_Reprocess').click(itemReprocess);
$('#HistoryEdit_Redownload').click(itemRedownload);
$('#HistoryEdit_RetryFailed').click(itemRetryFailed);
$('#HistoryEdit_Actions').click(itemActions);
$('#HistoryEdit_Param, #HistoryEdit_Dupe, #HistoryEdit_Log').click(tabClick);
$('#HistoryEdit_Back').click(backClick);
$('#HistoryEdit_MarkSuccess').click(itemSuccess);
$('#HistoryEdit_MarkGood').click(itemGood);
$('#HistoryEdit_MarkBad').click(itemBad);
LogTab.init('History');
@@ -1597,7 +1520,7 @@ var HistoryEditDialog = (new function()
TabDialog.extend($HistoryEditDialog);
}
this.showModal = function(hist)
this.showModal = function(hist, area)
{
Refresher.pause();
@@ -1727,18 +1650,10 @@ var HistoryEditDialog = (new function()
Util.show($('#HistoryEdit_DupeBackup').closest('.control-group'), hist.Kind === 'NZB');
$('#HistoryEdit_DupeMode').closest('.control-group').toggleClass('last-group', hist.Kind !== 'NZB');
Util.show('#HistoryEdit_Return', hist.RemainingFileCount > 0);
Util.show('#HistoryEdit_ReturnURL', hist.Kind === 'URL');
Util.show('#HistoryEdit_Redownload', hist.Kind === 'NZB');
Util.show('#HistoryEdit_RetryFailed', hist.Kind === 'NZB' && hist.FailedArticles > 0 && hist.RetryData);
Util.show('#HistoryEdit_PathGroup, #HistoryEdit_StatisticsGroup, #HistoryEdit_Reprocess', hist.Kind === 'NZB');
Util.show('#HistoryEdit_PathGroup, #HistoryEdit_StatisticsGroup', hist.Kind === 'NZB');
Util.show('#HistoryEdit_CategoryGroup', hist.Kind !== 'DUP');
Util.show('#HistoryEdit_DupGroup', hist.Kind === 'DUP');
var dupeCheck = Options.option('DupeCheck') === 'yes';
Util.show('#HistoryEdit_MarkSuccess', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'SUCCESS') || (hist.Kind === 'DUP' && hist.DupStatus !== 'SUCCESS')) &&
hist.Status.substr(0, 7) !== 'SUCCESS');
Util.show('#HistoryEdit_MarkGood', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'GOOD') || (hist.Kind === 'DUP' && hist.DupStatus !== 'GOOD')));
Util.show('#HistoryEdit_MarkBad', dupeCheck && hist.Kind !== 'URL');
Util.show('#HistoryEdit_Dupe', dupeCheck);
$('#HistoryEdit_CategoryGroup').toggleClass('control-group-last', hist.Kind === 'URL');
@@ -1757,8 +1672,6 @@ var HistoryEditDialog = (new function()
var postLog = hist.MessageCount > 0;
Util.show('#HistoryEdit_Log', postLog);
EditUI.buildDNZBLinks(curHist.Parameters ? curHist.Parameters : [], 'HistoryEdit_DNZB');
enableAllButtons();
$('#HistoryEdit_GeneralTab').show();
@@ -1776,6 +1689,13 @@ var HistoryEditDialog = (new function()
logFilled = false;
notification = null;
if (area === 'backup')
{
showing = true;
$('#HistoryEdit_ServStats').trigger('click');
}
showing = false;
$HistoryEditDialog.modal({backdrop: 'static'});
}
@@ -1842,14 +1762,14 @@ var HistoryEditDialog = (new function()
{
e.preventDefault();
$('#HistoryEdit_Back').fadeIn(500);
$('#HistoryEdit_Back').fadeIn(showing ? 0 : 500);
$('#HistoryEdit_BackSpace').hide();
var tab = '#' + $(this).attr('data-tab');
lastPage = $(tab);
lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme;
$HistoryEditDialog.switchTab($('#HistoryEdit_GeneralTab'), lastPage,
e.shiftKey || !UISettings.slideAnimation ? 0 : 500,
e.shiftKey || !UISettings.slideAnimation || showing ? 0 : 500,
{fullscreen: lastFullscreen, mini: UISettings.miniTheme});
if (tab === '#HistoryEdit_LogTab' && !logFilled && curHist.MessageCount > 0)
@@ -1887,78 +1807,6 @@ var HistoryEditDialog = (new function()
$('#HistoryEdit_Transmit').hide();
}
function itemDelete(e)
{
e.preventDefault();
HistoryUI.deleteConfirm(doItemDelete, curHist.Kind === 'NZB', curHist.Kind === 'DUP',
curHist.ParStatus === 'FAILURE' || curHist.UnpackStatus === 'FAILURE' ||
curHist.DeleteStatus != 'NONE', false);
}
function doItemDelete(command)
{
disableAllButtons();
notification = '#Notif_History_Deleted';
RPC.call('editqueue', [command, '', [curHist.ID]], completed);
}
function itemReturn(e)
{
e.preventDefault();
disableAllButtons();
notification = '#Notif_History_Returned';
RPC.call('editqueue', ['HistoryReturn', '', [curHist.ID]], completed);
}
function itemRedownload(e)
{
e.preventDefault();
if (curHist.SuccessArticles > 0)
{
ConfirmDialog.showModal('HistoryEditRedownloadConfirmDialog', doItemRedownload,
function () { HistoryUI.confirmMulti(false); });
}
else
{
doItemRedownload();
}
}
function doItemRedownload()
{
disableAllButtons();
notification = '#Notif_History_Returned';
RPC.call('editqueue', ['HistoryRedownload', '', [curHist.ID]], completed);
}
function itemReprocess(e)
{
e.preventDefault();
disableAllButtons();
saveCompleted = reprocess;
saveDupeKey();
}
function reprocess()
{
notification = '#Notif_History_Reprocess';
RPC.call('editqueue', ['HistoryProcess', '', [curHist.ID]], completed);
}
function itemRetryFailed(e)
{
e.preventDefault();
disableAllButtons();
saveCompleted = retryFailed;
saveDupeKey();
}
function retryFailed()
{
notification = '#Notif_History_RetryFailed';
RPC.call('editqueue', ['HistoryRetryFailed', '', [curHist.ID]], completed);
}
function completed()
{
$HistoryEditDialog.modal('hide');
@@ -2003,43 +1851,24 @@ var HistoryEditDialog = (new function()
: saveDupeKey();
}
function itemSuccess(e)
function itemActions(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditSuccessConfirmDialog', doItemSuccess, function () { HistoryUI.confirmMulti(false); });
}
e.stopPropagation();
var elem = $('#HistoryEdit_Actions').parent();
function doItemSuccess()
{
disableAllButtons();
notification = '#Notif_History_Marked';
RPC.call('editqueue', ['HistoryMarkSuccess', '', [curHist.ID]], completed);
}
function itemGood(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditGoodConfirmDialog', doItemGood, function () { HistoryUI.confirmMulti(false); });
}
function doItemGood()
{
disableAllButtons();
notification = '#Notif_History_Marked';
RPC.call('editqueue', ['HistoryMarkGood', '', [curHist.ID]], completed);
}
function itemBad(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditBadConfirmDialog', doItemBad, function () { HistoryUI.confirmMulti(false); });
}
function doItemBad()
{
disableAllButtons();
notification = '#Notif_History_Marked';
RPC.call('editqueue', ['HistoryMarkBad', '', [curHist.ID]], completed);
HistoryActionsMenu.showPopupMenu(curHist, 'top',
{ left: elem.offset().left, top: elem.offset().top - 1,
width: elem.width(), height: elem.height() + 2 },
function(_notification, actionCallback)
{
disableAllButtons();
notification = _notification;
saveCompleted = actionCallback;
saveName();
return true; // async
},
completed);
}
/*** TAB: POST-PROCESSING PARAMETERS **************************************************/

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
@@ -189,6 +189,14 @@ var FeedDialog = (new function($)
RPC.call('previewfeed', [id, name, url, filter, feedBacklog, feedPauseNzb, feedCategory,
feedPriority, feedInterval, feedScript, false, 0, ''], itemsLoaded, feedFailure);
}
if (!UISettings.miniTheme)
{
$('#FeedDialog_TableBlock').removeClass('modal-inner-scroll');
$('#FeedDialog_TableBlock').css('top', '');
$('#FeedDialog_TableBlock').css('top', $('#FeedDialog_TableBlock').position().top);
$('#FeedDialog_TableBlock').addClass('modal-inner-scroll');
}
}
function feedFailure(res)
@@ -266,6 +274,12 @@ var FeedDialog = (new function($)
else
{
var info = '<div class="check img-check"></div><span class="row-title">' + name + '</span>' + ' ' + status;
if (item.Category !== '')
{
info += ' <span class="label label-info">' + item.Category + '</span>';
}
info += ' <span class="label label-info">' + age + '</span>' +
' <span class="label label-info">' + size + '</span>';
fields = [info];
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -20,7 +20,8 @@
/*
* In this module:
* 1) History tab;
* 2) Functions for html generation for history, also used from other modules (edit dialog).
* 2) Functions for html generation for history, also used from other modules (edit dialog);
* 3) Popup menus in history list.
*/
/*** HISTORY TAB AND EDIT HISTORY DIALOG **********************************************/
@@ -34,6 +35,7 @@ var History = (new function($)
var $HistoryTabBadge;
var $HistoryTabBadgeEmpty;
var $HistoryRecordsPerPage;
var $CategoryMenu;
// State
var history;
@@ -51,6 +53,7 @@ var History = (new function($)
$HistoryTabBadge = $('#HistoryTabBadge');
$HistoryTabBadgeEmpty = $('#HistoryTabBadgeEmpty');
$HistoryRecordsPerPage = $('#HistoryRecordsPerPage');
$CategoryMenu = $('#HistoryCategoryMenu');
var recordsPerPage = UISettings.read('HistoryRecordsPerPage', 10);
$HistoryRecordsPerPage.val(recordsPerPage);
@@ -74,6 +77,11 @@ var History = (new function($)
});
$HistoryTable.on('click', 'a', editClick);
$HistoryTable.on('click', 'td:nth-child(2).dropdown-cell > div', statusClick);
$HistoryTable.on('click', 'td:nth-child(5).dropdown-cell > div:not(.dropdown-disabled)', categoryClick);
$CategoryMenu.on('click', 'a', categoryMenuClick);
HistoryActionsMenu.init();
}
this.applyTheme = function()
@@ -184,16 +192,14 @@ var History = (new function($)
var status = HistoryUI.buildStatus(hist);
var name = '<a href="#" histid="' + hist.ID + '">' + Util.textToHtml(Util.formatNZBName(hist.Name)) + '</a>';
var name = '<a href="#" data-nzbid="' + hist.ID + '">' + Util.textToHtml(Util.formatNZBName(hist.Name)) + '</a>';
name += DownloadsUI.buildEncryptedLabel(hist.Kind === 'NZB' ? hist.Parameters : []);
var dupe = DownloadsUI.buildDupe(hist.DupeKey, hist.DupeScore, hist.DupeMode);
var category = '';
if (hist.Kind !== 'DUP')
{
var category = Util.textToHtml(hist.Category);
}
var category = hist.Kind !== 'DUP' ?
(hist.Category !== '' ? Util.textToHtml(hist.Category) : '<span class="none-category">None</span>')
: '';
var backup = hist.Kind === 'NZB' ? DownloadsUI.buildBackupLabel(hist) : '';
if (hist.Kind === 'URL')
{
@@ -206,12 +212,14 @@ var History = (new function($)
if (!UISettings.miniTheme)
{
item.fields = ['<div class="check img-check"></div>', status, item.data.time, name + dupe, category, item.data.age, item.data.size];
status = '<div data-nzbid="' + hist.NZBID + '">' + status + '</div>';
category = '<div data-nzbid="' + hist.NZBID + '"' + (hist.Kind === 'DUP' ? ' class="dropdown-disabled"' : '') + '>' + category + '</div>';
item.fields = ['<div class="check img-check"></div>', status, item.data.time, name + dupe + backup, category, item.data.age, item.data.size];
}
else
{
var info = '<div class="check img-check"></div><span class="row-title">' + name + '</span>' + dupe +
' ' + status + ' <span class="label">' + item.data.time + '</span>';
' ' + status + backup + ' <span class="label">' + item.data.time + '</span>';
if (category)
{
info += ' <span class="label label-status">' + category + '</span>';
@@ -226,11 +234,19 @@ var History = (new function($)
function renderCellCallback(cell, index, item)
{
if (index === 2)
if (index === 1 || index === 4)
{
cell.className = 'text-center';
cell.className = !UISettings.miniTheme ? 'dropdown-cell' : '';
}
else if (index === 5 || index === 6)
else if (index === 2)
{
cell.className = 'text-center' + (!UISettings.miniTheme ? ' dropafter-cell' : '');
}
else if (index === 5)
{
cell.className = 'text-right' + (!UISettings.miniTheme ? ' dropafter-cell' : '');
}
else if (index === 6)
{
cell.className = 'text-right';
}
@@ -343,12 +359,7 @@ var History = (new function($)
{
Refresher.pause();
var ids = [];
var checkedRows = $HistoryTable.fasttable('checkedRows');
for (var id in checkedRows)
{
ids.push(parseInt(id));
}
var ids = buildContextIdList();
RPC.call('editqueue', [command, '', ids], function()
{
@@ -371,27 +382,86 @@ var History = (new function($)
e.preventDefault();
e.stopPropagation();
var histid = $(this).attr('histid');
var histid = $(this).attr('data-nzbid');
var area = $(this).attr('data-area');
$(this).blur();
var hist = null;
// find history object
for (var i=0; i<history.length; i++)
{
var gr = history[i];
if (gr.ID == histid)
{
hist = gr;
break;
}
}
var hist = findHist(histid);
if (hist == null)
{
return;
}
HistoryEditDialog.showModal(hist);
HistoryEditDialog.showModal(hist, area);
}
function findHist(nzbid)
{
for (var i=0; i<history.length; i++)
{
var gr = history[i];
if (gr.NZBID == nzbid)
{
return gr;
}
}
return null;
}
function buildContextIdList(hist)
{
var editIds = [];
var checkedRows = $HistoryTable.fasttable('checkedRows');
for (var id in checkedRows)
{
editIds.push(parseInt(id));
}
if (hist !== undefined && editIds.indexOf(hist.NZBID) === -1)
{
editIds = [hist.NZBID];
}
return editIds;
}
this.buildContextIdList = buildContextIdList;
function statusClick(e)
{
e.preventDefault();
e.stopPropagation();
var hist = findHist($(this).attr('data-nzbid'));
HistoryActionsMenu.showPopupMenu(hist, 'left',
{ left: $(this).offset().left - 30, top: $(this).offset().top,
width: $(this).width() + 30, height: $(this).outerHeight() - 2 },
function(_notification) { notification = _notification; },
editCompleted);
}
function categoryClick(e)
{
e.preventDefault();
e.stopPropagation();
DownloadsUI.fillCategoryMenu($CategoryMenu);
var hist = findHist($(this).attr('data-nzbid'));
var editIds = buildContextIdList(hist);
$CategoryMenu.data('nzbids', editIds);
DownloadsUI.updateContextWarning($CategoryMenu, editIds);
$('i', $CategoryMenu).removeClass('icon-ok').addClass('icon-empty');
$('li[data="' + hist.Category + '"] i', $CategoryMenu).addClass('icon-ok');
Frontend.showPopupMenu($CategoryMenu, 'bottom-left',
{ left: $(this).offset().left - 30, top: $(this).offset().top,
width: $(this).width() + 30, height: $(this).outerHeight() - 2 });
}
function categoryMenuClick(e)
{
e.preventDefault();
var category = $(this).parent().attr('data');
var nzbids = $CategoryMenu.data('nzbids');
notification = '#Notif_History_Changed';
RPC.call('editqueue', ['HistorySetCategory', category, nzbids], editCompleted);
}
function filterCallback(item)
@@ -563,6 +633,156 @@ var HistoryUI = (new function($)
}
}(jQuery));
/*** HISTORY ACTION MENU *************************************************************************/
var HistoryActionsMenu = (new function()
{
'use strict'
var $ActionsMenu;
var curHist;
var beforeCallback;
var completedCallback;
var editIds;
this.init = function()
{
$ActionsMenu = $('#HistoryActionsMenu');
$('#HistoryActions_Delete').click(itemDelete);
$('#HistoryActions_Return, #HistoryActions_ReturnURL').click(itemReturn);
$('#HistoryActions_Reprocess').click(itemReprocess);
$('#HistoryActions_Redownload').click(itemRedownload);
$('#HistoryActions_RetryFailed').click(itemRetryFailed);
$('#HistoryActions_MarkSuccess').click(itemSuccess);
$('#HistoryActions_MarkGood').click(itemGood);
$('#HistoryActions_MarkBad').click(itemBad);
}
this.showPopupMenu = function(hist, anchor, rect, before, completed)
{
curHist = hist;
beforeCallback = before;
completedCallback = completed;
editIds = History.buildContextIdList(hist);
// setup menu items
Util.show('#HistoryActions_Return', hist.RemainingFileCount > 0);
Util.show('#HistoryActions_ReturnURL', hist.Kind === 'URL');
Util.show('#HistoryActions_Redownload, #HistoryActions_Reprocess', hist.Kind === 'NZB');
Util.show('#HistoryActions_RetryFailed', hist.Kind === 'NZB' && hist.FailedArticles > 0 && hist.RetryData);
var dupeCheck = Options.option('DupeCheck') === 'yes';
Util.show('#HistoryActions_MarkSuccess', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'SUCCESS') || (hist.Kind === 'DUP' && hist.DupStatus !== 'SUCCESS')) &&
hist.Status.substr(0, 7) !== 'SUCCESS');
Util.show('#HistoryActions_MarkGood', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'GOOD') || (hist.Kind === 'DUP' && hist.DupStatus !== 'GOOD')));
Util.show('#HistoryActions_MarkBad', dupeCheck && hist.Kind !== 'URL');
DownloadsUI.updateContextWarning($ActionsMenu, editIds);
DownloadsUI.buildDNZBLinks(hist.Parameters ? hist.Parameters : [], 'HistoryActions_DNZB');
Frontend.showPopupMenu($ActionsMenu, anchor, rect);
}
function itemDelete(e)
{
e.preventDefault();
HistoryUI.deleteConfirm(doItemDelete, curHist.Kind === 'NZB', curHist.Kind === 'DUP',
curHist.ParStatus === 'FAILURE' || curHist.UnpackStatus === 'FAILURE' ||
curHist.DeleteStatus != 'NONE', false);
}
function execAction(command, notification)
{
function performAction()
{
RPC.call('editqueue', [command, '', editIds], completedCallback);
}
var async = beforeCallback(notification, performAction);
if (!async)
{
performAction();
}
}
function doItemDelete(command)
{
execAction(command, '#Notif_History_Deleted');
}
function itemReturn(e)
{
e.preventDefault();
execAction('HistoryReturn', '#Notif_History_Returned');
}
function itemRedownload(e)
{
e.preventDefault();
if (curHist.SuccessArticles > 0)
{
ConfirmDialog.showModal('HistoryEditRedownloadConfirmDialog', doItemRedownload,
function () { HistoryUI.confirmMulti(false); });
}
else
{
doItemRedownload();
}
}
function doItemRedownload()
{
execAction('HistoryRedownload', '#Notif_History_Returned');
}
function itemReprocess(e)
{
e.preventDefault();
execAction('HistoryProcess', '#Notif_History_Reprocess');
}
function itemRetryFailed(e)
{
e.preventDefault();
execAction('HistoryRetryFailed', '#Notif_History_RetryFailed');
}
function itemSuccess(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditSuccessConfirmDialog', doItemSuccess,
function () { HistoryUI.confirmMulti(editIds.length > 1); });
}
function doItemSuccess()
{
execAction('HistoryMarkSuccess', '#Notif_History_Marked');
}
function itemGood(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditGoodConfirmDialog', doItemGood,
function () { HistoryUI.confirmMulti(editIds.length > 1); });
}
function doItemGood()
{
execAction('HistoryMarkGood', '#Notif_History_Marked');
}
function itemBad(e)
{
e.preventDefault();
ConfirmDialog.showModal('HistoryEditBadConfirmDialog', doItemBad,
function () { HistoryUI.confirmMulti(editIds.length > 1); });
}
function doItemBad()
{
execAction('HistoryMarkBad', '#Notif_History_Marked');
}
}(jQuery));
/*** PURGE HISTORY DIALOG *****************************************************/
var PurgeHistoryDialog = (new function($)

View File

@@ -2,7 +2,7 @@
<!--
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -294,11 +294,11 @@
<thead>
<tr>
<th><div class="check img-check"></div></th>
<th width="14px" class="text-center"><a href="#" onclick="Downloads.sort('priority', event)" title="Priority">P</a></th>
<th width="95px">Status</th>
<th id="DownloadsTable_Name"><a href="#" onclick="Downloads.sort('name', event)">Name</a></th>
<th class="priority-cell"><a href="#" onclick="Downloads.sort('priority', event)" title="Priority">P</a></th>
<th width="102px">Status</th>
<th class="dropafter-cell" id="DownloadsTable_Name"><a href="#" onclick="Downloads.sort('name', event)">Name</a></th>
<th id="DownloadsTable_Category"><a href="#" onclick="Downloads.sort('category', event)">Category</a></th>
<th width="30px" class="text-right"><a href="#" onclick="Downloads.sort('age', event)">Age</a></th>
<th class="dropafter-cell" width="30px" class="text-right"><a href="#" onclick="Downloads.sort('age', event)">Age</a></th>
<th width="120px"><div style="float:left; position:relative; width:50%; text-align:left;"><a href="#"onclick="Downloads.sort('size', event)">Size</a></div><div style="float:right; position:relative; width:50%; text-align:right;"><a href="#" onclick="Downloads.sort('left', event)">Left</a></div></th>
<th width="60px" class="text-right">Est. Time</th>
</tr>
@@ -386,11 +386,11 @@
<thead>
<tr>
<th><div class="check img-check"></div></th>
<th width="75px">Status</th>
<th width="170px" class="text-center">Time</th>
<th width="78px">Status</th>
<th width="170px" class="text-center dropafter-cell">Time</th>
<th>Name</th>
<th id="HistoryTable_Category">Category</th>
<th width="30px" class="text-right">Age</th>
<th width="30px" class="text-right dropafter-cell">Age</th>
<th width="70px" class="text-right">Size</th>
</tr>
<tr>
@@ -550,7 +550,7 @@
There are many configuration options affecting performance. If you use
NZBGet on a computer with limited capabilities, such as NAS, media player,
router, etc. you should take your time to configure NZBGet for best
performance - see <a href="http://nzbget.net/Performance_tips">Performance tips</a>.
performance - see <a href="http://nzbget.net/performance-tips">Performance tips</a>.
</p>
<h4>Extension scripts settings</h4>
@@ -1065,7 +1065,7 @@
<p><span class="label label-warning">NOTE:</span>
This is a short documentation. For more info visit
<a href="http://nzbget.net/Quick_filter">nzbget.net/Quick_filter</a>.</p>
<a href="http://nzbget.net/quick-filter">nzbget.net/quick-filter</a>.</p>
</div>
</div>
@@ -1285,17 +1285,7 @@
<div class="modal-footer">
<div class="btn-group dropup pull-left">
<a class="btn dropdown-toggle" data-toggle="dropdown">Actions <span class="caret"></span></a>
<ul class="dropdown-menu footer-button-menu" id="DownloadsEdit_ActionsMenu">
<li class="menu-header">Actions</li>
<li><a href="#" id="DownloadsEdit_Resume"><i class="icon-play"></i> Resume</a></li>
<li><a href="#" id="DownloadsEdit_Pause"><i class="icon-pause"></i> Pause</a></li>
<li><a href="#" id="DownloadsEdit_Delete"><i class="icon-trash"></i> Delete</a></li>
<li><a href="#" id="DownloadsEdit_CancelPP"><i class="icon-remove"></i> Cancel Post-Processing</a></li>
<li class="menu-header" id="DownloadsEdit_DNZB_Section">Indexer Links</li>
<li><a href="#" target="_blank" class="DownloadsEdit_DNZB" id="DownloadsEdit_DNZB_Details"><i class="icon-postcard"></i> Details</a></li>
<li><a href="#" target="_blank" class="DownloadsEdit_DNZB" id="DownloadsEdit_DNZB_MoreInfo"><i class="icon-link"></i> More Info</a></li>
</ul>
<a class="btn" id="DownloadsEdit_Actions">Actions <span class="caret"></span></a>
</div>
<div class="dialog-transmit hide" style="position:absolute;margin-left:260px;" id="DownloadsEdit_Transmit"><img src="img/transmit.gif"></div>
@@ -1304,6 +1294,39 @@
</div>
</div>
<!-- *** PRIORITY DROP DOWN MENU -->
<ul class="dropdown-menu" id="PriorityMenu">
<li class="menu-header">Priority</li>
<li class="dropdown-warning">XX records selected</li>
<li data="900"><a href="#"><table><tr><td><i class="icon-empty"></td><td>Force</td></tr></table></a></li>
<li data="100"><a href="#"><table><tr><td><i class="icon-empty"></td><td>Very High</td></tr></table></a></li>
<li data="50"><a href="#"><table><tr><td><i class="icon-empty"></td><td>High</td></tr></table></a></li>
<li data="0"><a href="#"><table><tr><td><i class="icon-ok"></td><td>Normal</td></tr></table></a></li>
<li data="-50"><a href="#"><table><tr><td><i class="icon-empty"></td><td>Low</td></tr></table></a></li>
<li data="-100"><a href="#"><table><tr><td><i class="icon-emptyok"></td><td>Very Low</td></tr></table></a></li>
</ul>
<!-- *** DOWNLOADS ACTIONS DROP DOWN MENU -->
<ul class="dropdown-menu" id="DownloadsActionsMenu">
<li class="menu-header">Actions</li>
<li class="dropdown-warning">XX records selected</li>
<li><a href="#" id="DownloadsActions_Resume"><i class="icon-play"></i> Resume</a></li>
<li><a href="#" id="DownloadsActions_Pause"><i class="icon-pause"></i> Pause</a></li>
<li><a href="#" id="DownloadsActions_Delete"><i class="icon-trash"></i> Delete</a></li>
<li><a href="#" id="DownloadsActions_CancelPP"><i class="icon-remove"></i> Cancel Post-Processing</a></li>
<li class="menu-header" id="DownloadsActions_DNZB_Section">Indexer Links</li>
<li><a href="#" target="_blank" class="DownloadsActions_DNZB" id="DownloadsActions_DNZB_Details"><i class="icon-postcard"></i> Details</a></li>
<li><a href="#" target="_blank" class="DownloadsActions_DNZB" id="DownloadsActions_DNZB_MoreInfo"><i class="icon-link"></i> More Info</a></li>
</ul>
<!-- *** DOWNLAODS CATEGORY DROP DOWN MENU -->
<ul class="dropdown-menu" id="DownloadsCategoryMenu">
<li class="menu-header">Category</li>
<li class="dropdown-warning">XX records selected</li>
<li data=""><a href="#"><table><tr><td><i class="icon-empty"></td><td class="empty-item">None</td></tr></table></a></li>
<li style="display: none;"><a href="#"><table><tr><td><i class="icon-empty"></td><td>$Name</td></tr></table></a></li>
</ul>
<!-- *** MULTI EDIT DOWNLOAD DIALOG ************************************************************ -->
<div class="modal hide" id="DownloadsMultiDialog">
@@ -1657,23 +1680,7 @@
<div class="modal-footer">
<div class="btn-group dropup pull-left">
<a class="btn dropdown-toggle" data-toggle="dropdown">Actions <span class="caret"></span></a>
<ul class="dropdown-menu footer-button-menu" id="HistoryEdit_ActionsMenu">
<li class="menu-header">Actions</li>
<li><a href="#" id="HistoryEdit_Delete"><i class="icon-trash"></i> Delete</a></li>
<li><a href="#" id="HistoryEdit_Reprocess"><i class="icon-process"></i> Post-Process Again</a></li>
<li><a href="#" id="HistoryEdit_Return"><i class="icon-downloads"></i> Download Remaining Files</a></li>
<li><a href="#" id="HistoryEdit_ReturnURL"><i class="icon-downloads"></i> Download Again</a></li>
<li><a href="#" id="HistoryEdit_Redownload"><i class="icon-downloads"></i> Download Again</a></li>
<li><a href="#" id="HistoryEdit_RetryFailed"><i class="icon-downloads"></i> Retry Failed Articles</a></li>
<li><a href="#" id="HistoryEdit_MarkSuccess"><i class="icon-ok"></i> Mark as Success</a></li>
<li><a href="#" id="HistoryEdit_MarkGood"><i class="icon-plus"></i> Mark as Good</a></li>
<li><a href="#" id="HistoryEdit_MarkBad"><i class="icon-minus"></i> Mark as Bad</a></li>
<li class="menu-header" id="HistoryEdit_DNZB_Section">Indexer Links</li>
<li><a href="#" target="_blank" class="HistoryEdit_DNZB" id="HistoryEdit_DNZB_Details"><i class="icon-postcard"></i> Details</a></li>
<li><a href="#" target="_blank" class="HistoryEdit_DNZB" id="HistoryEdit_DNZB_MoreInfo"><i class="icon-link"></i> More Info</a></li>
<li><a href="#" target="_blank" class="HistoryEdit_DNZB" id="HistoryEdit_DNZB_Report"><i class="icon-alert"></i> Report This NZB</a></li>
</ul>
<a class="btn dropdown-toggle" id="HistoryEdit_Actions" data-toggle="dropdown">Actions <span class="caret"></span></a>
</div>
<div class="dialog-transmit" style="position:absolute;margin-left:320px;" id="HistoryEdit_Transmit"><img src="img/transmit.gif"></div>
@@ -1682,6 +1689,33 @@
</div>
</div>
<!-- *** HISTORY ACTIONS DROP DOWN MENU -->
<ul class="dropdown-menu" id="HistoryActionsMenu">
<li class="menu-header">Actions</li>
<li class="dropdown-warning">XX records selected</li>
<li><a href="#" id="HistoryActions_Delete"><i class="icon-trash"></i> Delete</a></li>
<li><a href="#" id="HistoryActions_Reprocess"><i class="icon-process"></i> Post-Process Again</a></li>
<li><a href="#" id="HistoryActions_Return"><i class="icon-downloads"></i> Download Remaining Files</a></li>
<li><a href="#" id="HistoryActions_ReturnURL"><i class="icon-downloads"></i> Download Again</a></li>
<li><a href="#" id="HistoryActions_Redownload"><i class="icon-downloads"></i> Download Again</a></li>
<li><a href="#" id="HistoryActions_RetryFailed"><i class="icon-downloads"></i> Retry Failed Articles</a></li>
<li><a href="#" id="HistoryActions_MarkSuccess"><i class="icon-ok"></i> Mark as Success</a></li>
<li><a href="#" id="HistoryActions_MarkGood"><i class="icon-plus"></i> Mark as Good</a></li>
<li><a href="#" id="HistoryActions_MarkBad"><i class="icon-minus"></i> Mark as Bad</a></li>
<li class="menu-header" id="HistoryActions_DNZB_Section">Indexer Links</li>
<li><a href="#" target="_blank" class="HistoryActions_DNZB" id="HistoryActions_DNZB_Details"><i class="icon-postcard"></i> Details</a></li>
<li><a href="#" target="_blank" class="HistoryActions_DNZB" id="HistoryActions_DNZB_MoreInfo"><i class="icon-link"></i> More Info</a></li>
<li><a href="#" target="_blank" class="HistoryActions_DNZB" id="HistoryActions_DNZB_Report"><i class="icon-alert"></i> Report This NZB</a></li>
</ul>
<!-- *** HISTORY CATEGORY DROP DOWN MENU -->
<ul class="dropdown-menu" id="HistoryCategoryMenu">
<li class="menu-header">Category</li>
<li class="dropdown-warning">XX records selected</li>
<li data=""><a href="#"><table><tr><td><i class="icon-empty"></td><td class="empty-item">None</td></tr></table></a></li>
<li style="display: none;"><a href="#"><table><tr><td><i class="icon-empty"></td><td>$Name</td></tr></table></a></li>
</ul>
<!-- *** FEED VIEW DIALOG ************************************************************ -->
<div class="modal hide modal-max" id="FeedDialog">
@@ -1724,7 +1758,7 @@
<div class="pull-right" style="margin-bottom:10px;"><input id="FeedDialog_ItemTable_filter" class="search-query" placeholder="Search" type="text"></div>
</div>
<div class="modal-inner-scroll">
<div id="FeedDialog_TableBlock">
<table class="table table-striped table-bordered table-check table-cancheck datatable" id="FeedDialog_ItemTable" style="margin-bottom:15px;">
<thead><tr><th><div class="check img-check"></div></th><th>Status</th><th id="FeedDialog_ItemTable_Name">Title</th><th>Category</th><th class="text-right">Age</th><th class="text-right">Size</th></tr></thead>
<tbody></tbody>
@@ -2030,7 +2064,7 @@
<p><span class="label label-warning">NOTE:</span>
This is a short documentation. For more info visit
<a href="http://nzbget.net/RSS">nzbget.net/RSS</a>.
<a href="http://nzbget.net/rss">nzbget.net/rss</a>.
</p>
</div>
</div>
@@ -2258,6 +2292,21 @@
<div id="StatDialogResetConfirmDialog_OK">Reset</div>
</div>
<!-- *** START DANGEROUS SCRIPT CONFIRMATION DIALOG DATA ******************************** -->
<div class="hide" id="DangerScriptConfirmDialog">
<div id="DangerScriptConfirmDialog_Title">Execute Script</div>
<div id="DangerScriptConfirmDialog_Text">
<p>
Please confrim execution of the script with command "<span id="DangerScriptConfirmDialog_Command"></span>".
</p>
<p class="confirm-help-block">
This script command is marked as dangerous and requires a confirmation.
</p>
</div>
<div id="DangerScriptConfirmDialog_OK">Execute Script</div>
</div>
<!-- *** PURGE HISTORY DIALOG ************************************** -->
<div class="modal modal-mini modal-padded modal-center hide" id="PurgeHistoryDialog">
@@ -2300,7 +2349,8 @@
<div class="control-group">
<label class="control-label" for="AddDialog_URL" id="AddDialog_URLLabel"><i class="icon-remove"></i><img class="hide" src="img/transmit-file.gif" width="16px" height="16px"> Add from URL</label>
<div class="controls">
<input type="text" class="input-xlarge" id="AddDialog_URL" />
<input type="text" class="input-xblarge" id="AddDialog_URL" />&nbsp;
<a href="#" id="AddDialog_URLProp" title="Set name or password" onclick="Upload.renameURLClick()"><i class="icon-edit"></i></a>
</div>
</div>
@@ -2310,6 +2360,7 @@
<div style="margin-top:6px; margin-bottom:10px;"><a href="#" class="btn" style="margin-top: -6px;" id="AddDialog_Select">Select files</a> or drop files here.</div>
<input multiple="multiple" type="file" id="AddDialog_Input" class="hidden-file-input">
<div class="dialog-add-files hide" id="AddDialog_Files"></div>
<div class="hide" style="margin-top:12px;" id="AddDialog_RenameTip">Tip: click on an item to rename or set password.</div>
</div>
</div>
@@ -2596,7 +2647,7 @@
<p class="alert alert-info">
If you compiled the program yourself you know how to update.
If you installed a binary package ask maintainer to add support for automatic updates.
If you create a public package please read <a href="http://nzbget.net/Packaging" target="_blank">"About Packaging"</a>.
If you create a public package please read <a href="http://nzbget.net/packaging" target="_blank">"About Packaging"</a>.
</p>
</div>
@@ -2641,7 +2692,7 @@
<!-- *** INSTALL DIALOG ************************************************** -->
<div class="modal modal-center hide" id="UpdateProgressDialog">
<div class="modal modal-center hide log-dialog" id="UpdateProgressDialog">
<div class="modal-header">
<a class="close" href='#' data-dismiss="modal"><i class="icon-close"></i></a>
<h3>Updating NZBGet</h3>
@@ -2652,6 +2703,23 @@
</div>
</div>
<!-- *** EXEC SCRIPT DIALOG ************************************************** -->
<div class="modal modal-center hide log-dialog" id="ExecScriptDialog">
<div class="modal-header">
<a class="close" href='#' data-dismiss="modal"><i class="icon-close"></i></a>
<h3 id="ExecScriptDialog_Title">Executing script</h3>
</div>
<div class="modal-body">
<pre id="ExecScriptDialog_Log">
</pre>
</div>
<div class="modal-footer">
<div class="pull-left" id="ExecScriptDialog_Status">You may close this dialog any time, the script remains running in background.</div>
<a href="#" class="btn btn-primary" data-dismiss="modal">Close</a>
</div>
</div>
<!-- *** ERROR DIALOG ************************************************** -->
<div class="modal modal-mini modal-padded modal-center hide" id="AlertDialog">
@@ -2719,6 +2787,44 @@
<div id="ShutdownConfirmDialog_OK">Shutdown</div>
</div>
<!-- *** WEB LOGIN DIALOG ************************************** -->
<div class="modal modal-mini modal-center hide" id="LoginDialog">
<div class="modal-header">
<h3>NZBGet Login</h3>
</div>
<form name="LoginDialog_Form" id="LoginDialog_Form" action="index.html">
<div class="modal-body">
<div class="form-horizontal">
<fieldset>
<div class="control-group">
<label class="control-label" for="LoginDialog_UsernameGroup">Username</label>
<div class="controls" id="LoginDialog_UsernameGroup">
<input class="input-medium" id="LoginDialog_Username" type="text">
</div>
</div>
<div class="control-group last-group" id="LoginDialog_PasswordBlock">
<label class="control-label" for="LoginDialog_PasswordGroup">Password</label>
<div class="controls" id="LoginDialog_PasswordGroup">
<input class="input-medium" id="LoginDialog_Password" type="password">
</div>
</div>
<p class="alert alert-danger hide text-center" id="LoginDialog_Error">
It didn't work. Try again.
</p>
</fieldset>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="LoginDialog_Login">Login</button>
</div>
</form>
</div>
<!-- *** DRAG-N-DROP ************************************************************ -->
<div id="TableDragBox"><span id="TableDragBadge" class="badge"></span><div id="TableDragContent"></div></div>
@@ -2741,6 +2847,10 @@
<strong>Saved</strong>
</div>
<div id="Notif_Downloads_Changed" data-duration="1000" class="alert alert-inverse alert-center alert-center-small hide">
<strong>Changed</strong>
</div>
<div id="Notif_Downloads_Pausing" data-duration="1000" class="alert alert-inverse alert-center alert-center-small hide">
<strong>Pausing</strong>
</div>
@@ -2817,6 +2927,10 @@
<strong>Deleted</strong>
</div>
<div id="Notif_History_Changed" data-duration="1000" class="alert alert-inverse alert-center alert-center-small hide">
<strong>Changed</strong>
</div>
<div id="Notif_History_Returned" data-duration="1000" class="alert alert-inverse alert-center alert-center-small hide">
<strong>Returned to Queue</strong>
</div>

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -174,6 +174,7 @@ var Frontend = (new function($)
var switchingTheme = false;
var activeTab = 'Downloads';
var lastTab = '';
var lastMenu = $();
this.init = function()
{
@@ -206,6 +207,7 @@ var Frontend = (new function($)
ConfigBackupRestore.init();
ConfirmDialog.init();
UpdateDialog.init();
ExecScriptDialog.init();
AlertDialog.init();
ScriptListDialog.init();
RestoreSettingsDialog.init();
@@ -222,7 +224,7 @@ var Frontend = (new function($)
initialized = true;
Refresher.update();
authorize();
}
function initControls()
@@ -507,6 +509,50 @@ var Frontend = (new function($)
}
this.alignPopupMenu = alignPopupMenu;
function showPopupMenu(menu, anchor, rect)
{
var $menu = $(menu);
if ($menu.is(':visible'))
{
$menu.hide();
return;
}
lastMenu.hide();
lastMenu = $menu;
$menu.css({
left: rect.left + (anchor.indexOf('right') > -1 ? rect.width - $menu.outerWidth() : 0),
top: rect.top + (anchor.indexOf('top') > -1 ? - $menu.outerHeight() : rect.height)
});
$menu.show();
if ($menu.offset().top < $(window).scrollTop())
{
$menu.css({ top: rect.top + rect.height });
}
if ($menu.offset().left + $menu.outerWidth() > $(window).width())
{
$menu.css({ left: $(window).width() - $menu.outerWidth() });
}
if ($menu.offset().top + $menu.outerHeight() > $(window).height() + $(window).scrollTop())
{
$menu.css({ top: rect.top - $menu.outerHeight() });
}
if ($menu.offset().top < $(window).scrollTop())
{
$menu.css({ top: $(window).scrollTop() });
}
if (UISettings.miniTheme)
{
alignPopupMenu($menu);
}
$('html').on('click.PopupMenu', function () { $menu.hide(); $('html').off('click.PopupMenu'); });
}
this.showPopupMenu = showPopupMenu;
function alignCenterDialogs()
{
$.each($('.modal-center'), function(index, element) {
@@ -621,6 +667,63 @@ var Frontend = (new function($)
switchingTheme = false;
}
function authorize()
{
var formAuth = document.cookie.indexOf('Auth-Type=form') > -1;
if (!formAuth)
{
Refresher.update();
return;
}
function sendAuth()
{
var username = $('#LoginDialog_Username').val();
var password = $('#LoginDialog_Password').val();
var headers = [{name: 'X-Authorization', value: 'Basic ' + window.btoa(username + ':' + password)}];
RPC.call('version', [],
function(version)
{
$('#LoginDialog').modal('hide');
// reloading of page is needed for certain browsers to force save-password-dialog
document.location.reload();
},
function(err, result)
{
$('#LoginDialog_PasswordBlock').removeClass('last-group');
$('#LoginDialog_Error').show();
if (!$('#LoginDialog_Password').is(":focus"))
{
$('#LoginDialog_Username').focus();
}
},
0, headers);
}
$('#LoginDialog_Form').submit(function(e)
{
if ($('#LoginDialog_Error').is(":visible"))
{
$('#LoginDialog_Error').hide();
$('#LoginDialog_PasswordBlock').addClass('last-group');
setTimeout(sendAuth, 500);
}
else
{
setTimeout(sendAuth, 0);
}
return false;
});
// try RPC call, it may work without extra authorization
RPC.call('version', [], Refresher.update, function()
{
$('#LoginDialog').modal({backdrop: 'static'});
$('#LoginDialog_Username').focus();
}, 10000);
}
}(jQuery));

View File

@@ -172,6 +172,11 @@ var Messages = (new function($)
}
var text = Util.textToHtml(message.Text);
// replace URLs
var exp = /(http:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
text = text.replace(exp, "<a href='$1' target='_blank'>$1</a>");
if (!item.time)
{
item.time = Util.formatDateTime(message.Time + UISettings.timeZoneCorrection*60*60);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -562,6 +562,7 @@ var StatDialog = (new function($)
var chartData = null;
var mouseOverIndex = -1;
var clockOK = false;
var volumeMode = false;
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
@@ -647,8 +648,11 @@ var StatDialog = (new function($)
});
}
this.showModal = function()
this.showModal = function(serverId)
{
volumeMode = serverId !== undefined;
curServer = serverId !== undefined ? serverId : 0;
$('#StatDialog_GeneralTab').show();
$('#StatDialog_VolumesTab').hide();
$('#StatDialog_Back').hide();
@@ -663,6 +667,8 @@ var StatDialog = (new function($)
$('#StatDialog_MonthTitle').text('Billing month:');
}
$('#StatDialog_Volume_MONTH, #StatDialog_Volume_MONTH2').text(monthNames[(new Date()).getMonth()] + ' ' + (new Date()).getFullYear());
monthListInitialized = false;
updateServerList();
lastTab = null;
@@ -671,6 +677,11 @@ var StatDialog = (new function($)
redrawStatistics();
$StatDialog.modal();
firstLoadStatisticsData();
if (volumeMode)
{
$('#StatDialog_Volumes').click();
}
}
this.redraw = function()
@@ -750,21 +761,27 @@ var StatDialog = (new function($)
{
e.preventDefault();
$('#StatDialog_Back').fadeIn(500);
$('#StatDialog_BackSpace').hide();
if (!volumeMode)
{
$('#StatDialog_Back').fadeIn(500);
$('#StatDialog_BackSpace').hide();
}
lastTab = '#' + $(this).attr('data-tab');
lastPage = $(lastTab);
lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme;
redrawLock++;
$StatDialog.switchTab($('#StatDialog_GeneralTab'), lastPage,
e.shiftKey || !UISettings.slideAnimation ? 0 : 500,
e.shiftKey || !UISettings.slideAnimation || volumeMode ? 0 : 500,
{ fullscreen: lastFullscreen,
toggleClass: 'modal-mini modal-large',
mini: UISettings.miniTheme,
complete: tabSwitchCompleted});
if (lastTab === '#StatDialog_VolumesTab')
{
redrawChart();
if (servervolumes)
{
redrawChart();
}
$('#StatDialog_Title').text('Downloaded volumes');
}
}

View File

@@ -1,7 +1,7 @@
/*!
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-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
@@ -747,15 +747,10 @@ ul.dropdown-menu > li > a > i {
margin-right: 10px;
}
.label {
/* -ms-padding-bottom: 1px; */
}
.label-status {
text-transform: uppercase;
}
.label-inline {
display: inline-block;
margin-bottom: -4px;
@@ -1054,7 +1049,6 @@ table.table-check > tbody > tr > td:first-child {
div.check {
width: 12px;
height: 12px;
border-color: #DDDDDD;
border: 1px solid #DDDDDD;
margin-top: 2px;
margin-bottom: 3px;
@@ -1062,12 +1056,14 @@ div.check {
-moz-border-radius: 2px;
border-radius: 2px;
}
div.check:hover {
border-color: #0088cc;
border: 1px solid #0088cc;
}
th > div.check {
margin-bottom: 2px;
}
table.table-cancheck tr div.img-check {
background-position: 10px 10px;
}
@@ -1167,6 +1163,95 @@ table.table-hidecheck tbody > tr > td:first-child {
}
/* END: Progress bars */
/* DROP-DOWN CONTEXT MENUS */
td.dropdown-cell {
padding: 0;
}
th.dropafter-cell,
td.dropafter-cell {
padding-left: 0;
}
td.dropdown-cell > div {
padding: 5px 12px 6px 4px;
margin: 0px 0;
display: inline-block;
position: relative;
}
td.dropdown-cell > div:hover {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
td.dropdown-cell > div:not(.dropdown-disabled):hover {
cursor: pointer;
}
td.dropdown-cell > div:not(.dropdown-disabled):hover::after {
border-top: 4px solid #888;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
content: "";
position: absolute;
margin-bottom: 8px;
margin-left: 2px;
}
.dropdown-menu {
z-index: 3000;
}
.dropdown-menu li:not(:hover) .empty-item {
color: #bbb;
}
.dropdown-warning {
text-align: center;
background: repeating-linear-gradient(-45deg, #FFFFD8, #FFFFD8 6px, #E7E8D1 6px, #E7E8D1 12px);
padding: 2px;
font-size: 12px;
line-height: 14px;
color: #966C38;
text-shadow: 0 -1px 0 rgba(255, 255, 255, 0.75), 0 1px 0 rgba(255, 255, 255, 0.75), -1px 0 0 rgba(255, 255, 255, 0.75);
border-top: solid 1px #ccc;
border-bottom: solid 1px #ccc;
margin-bottom: 1px;
}
table th.priority-cell {
padding-left: 8px;
}
table td.priority-cell {
width: 16px;
}
td.dropdown-cell.priority-cell > div {
padding-right: 7px;
}
td.dropdown-cell.priority-cell > div:not(.dropdown-disabled):hover::after {
margin-left: -1px;
}
#DownloadsTable td:first-child {
padding-right: 6px;
}
td span.none-category {
color: transparent;
}
td:hover span.none-category {
color: #aaa;
}
/* controls */
input[readonly],
select[readonly],
textarea[readonly],
@@ -1257,6 +1342,10 @@ textarea[readonly],
width: 350px;
}
.modal .input-xblarge {
width: 335px;
}
.modal.modal-padded .input-xxlarge {
width: 470px;
}
@@ -1330,9 +1419,6 @@ textarea[readonly],
background-color: #dff0d8;
}
.dialog-add-files {
}
ul.help > li {
margin-bottom: 10px;
}
@@ -1449,10 +1535,6 @@ ul.help > li {
left: auto;
}
.footer-button-menu {
text-align: left;
}
.data-statistics {
width: 300px;
}
@@ -1818,17 +1900,17 @@ div.multiset-toolbar button {
background-color: #ffffff;
}
#UpdateProgressDialog {
.log-dialog {
width: 640px;
margin-left: -320px;
}
#UpdateProgressDialog .modal-body {
.log-dialog .modal-body {
min-height: 280px;
position: relative;
}
#UpdateProgressDialog_Log {
.log-dialog .modal-body pre {
min-height: 270px;
background-color: #222222;
color: #cccccc;
@@ -1845,8 +1927,13 @@ div.multiset-toolbar button {
border-radius: 3px;
}
.update-log-error {
color: #ff0000;
.update-log-error,
.script-log-error {
color: #dd0000;
}
.script-log-success {
color: #00dd00;
}
/* FEED FILTER DIALOG */
@@ -2005,7 +2092,6 @@ div.multiset-toolbar button {
top: -20px;
padding: 6px 10px;
border-radius: 12px;
margin
}
#TableDragBox .table-bordered {
@@ -2452,6 +2538,14 @@ body.phone,
min-width: 270px;
}
.phone .dropdown-context:hover {
cursor: inherit;
}
.phone .dropdown-context:hover::after {
display: none;
}
/* TOOLBAR AND INPUTS */
.phone .btn-toolbar .btn,
.phone .btn-toolbar input {

View File

@@ -42,6 +42,7 @@ var Upload = (new function($)
var needRefresh = false;
var failure_message = null;
var url = '';
var urlInfo;
this.init = function()
{
@@ -211,7 +212,7 @@ var Upload = (new function($)
var info = { name: name, ext: ext, password: '', dupekey: '', dupescore: 0 };
infos.push(info);
}
$('#AddDialog_Files').show();
$('#AddDialog_Files,#AddDialog_RenameTip').show();
$('#AddDialog_FilesHelp').hide();
}
@@ -229,6 +230,11 @@ var Upload = (new function($)
});
}
this.renameURLClick = function(no)
{
AddParamDialog.showModal(urlInfo, function(){});
}
function showModal(droppedFiles)
{
Refresher.pause();
@@ -249,6 +255,7 @@ var Upload = (new function($)
files = [];
filesSuccess = [];
urlInfo = { name: '', password: '', dupekey: '', dupescore: 0 };
if (droppedFiles)
{
@@ -394,12 +401,19 @@ var Upload = (new function($)
$('#AddDialog_URLLabel img').show();
$('#AddDialog_URLLabel i').hide();
var name = urlInfo.name.toLowerCase();
if (name !== '' && !(Util.endsWith(name, '.nzb') || Util.endsWith(name, '.zip') ||
Util.endsWith(name, '.7z') || Util.endsWith(name, '.rar') || Util.endsWith(name, '.tar.gz')))
{
urlInfo.name += '.nzb';
}
var category = $('#AddDialog_Category').val();
var priority = parseInt($('#AddDialog_Priority').val());
var addPaused = $('#AddDialog_Paused').is(':checked');
var dupeMode = $('#AddDialog_DupeForce').is(':checked') ? "FORCE" : "SCORE";
RPC.call('append', ['', url, category, priority, false, addPaused, '', 0, dupeMode], urlCompleted, urlFailure);
var params = urlInfo.password === '' ? [] : [{'*Unpack:password' : urlInfo.password}];
RPC.call('append', [urlInfo.name, url, category, priority, false, addPaused, urlInfo.dupekey, urlInfo.dupescore, dupeMode, params], urlCompleted, urlFailure);
}
function urlCompleted(result)

View File

@@ -337,6 +337,11 @@ var Util = (new function($)
return target.tagName == 'TEXTAREA';
}
this.endsWith = function(text, substr)
{
return text.substring(text.length - substr.length, text.length) === substr;
}
}(jQuery));
@@ -528,9 +533,8 @@ var RPC = (new function($)
this.rpcUrl;
this.defaultFailureCallback;
this.connectErrorMessage = 'Cannot establish connection';
var XAuthToken;
this.call = function(method, params, completed_callback, failure_callback, timeout)
this.call = function(method, params, completed_callback, failure_callback, timeout, custom_headers)
{
var _this = this;
@@ -539,20 +543,15 @@ var RPC = (new function($)
xhr.open('post', this.rpcUrl);
if (XAuthToken !== undefined)
{
xhr.setRequestHeader('X-Auth-Token', XAuthToken);
}
if (timeout)
{
xhr.timeout = timeout;
}
// Example for cross-domain access:
//xhr.open('post', 'http://localhost:6789/jsonrpc');
//xhr.withCredentials = 'true';
//xhr.setRequestHeader('Authorization', 'Basic ' + window.btoa('myusername:mypassword'));
for (var i = 0; i < (custom_headers ? custom_headers.length : 0); i++)
{
xhr.setRequestHeader(custom_headers[i].name, custom_headers[i].value);
}
xhr.onreadystatechange = function()
{
@@ -576,7 +575,6 @@ var RPC = (new function($)
{
if (result.error == null)
{
XAuthToken = xhr.getResponseHeader('X-Auth-Token');
res = result.result;
completed_callback(res);
return;

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