mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-15 01:23:20 -05:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0eedc342b | ||
|
|
eb4b8b30e1 | ||
|
|
593e29f163 | ||
|
|
c4d29bc57f | ||
|
|
92db424ce0 | ||
|
|
17fbb795c8 | ||
|
|
35e65e792b | ||
|
|
486b9d7d2b | ||
|
|
3abaa0fb3f | ||
|
|
810ddc8356 | ||
|
|
1a840f894e | ||
|
|
928e0a6006 | ||
|
|
a4cca673dc | ||
|
|
fb9b84a23b | ||
|
|
bc2b9de6a9 | ||
|
|
7793f64b77 | ||
|
|
cad3116b5b | ||
|
|
dd714355c4 | ||
|
|
0a73a0c31f | ||
|
|
7f393d050c | ||
|
|
5dcca7294f | ||
|
|
53da5725c2 | ||
|
|
77cabd7bce | ||
|
|
2336d4bcfe | ||
|
|
580e1974bc | ||
|
|
8790ee685f | ||
|
|
32f0bbae58 | ||
|
|
8ffd6c24fe | ||
|
|
98cc4817fe | ||
|
|
14b40d6712 | ||
|
|
629640898d | ||
|
|
5df06a2626 | ||
|
|
4ca95b2989 | ||
|
|
b3cc316092 | ||
|
|
298fde6cd4 | ||
|
|
015b3be461 | ||
|
|
f6adbe848d | ||
|
|
08de827d7b | ||
|
|
d1bda91954 | ||
|
|
e40e3178da | ||
|
|
cf3985f228 | ||
|
|
f2329dada4 | ||
|
|
a73d1ba56e | ||
|
|
8d5ce3ddc3 | ||
|
|
d3362f9280 | ||
|
|
85dcde36c5 | ||
|
|
716bb3130f | ||
|
|
725e2c7376 | ||
|
|
3a07dd378a | ||
|
|
ff02b53ed0 | ||
|
|
39089b6f2f | ||
|
|
61af2b3446 | ||
|
|
20f4f3020b | ||
|
|
df5a1fe959 | ||
|
|
6b546394b2 | ||
|
|
160b274ce8 | ||
|
|
4104a2357b | ||
|
|
ba1e51a8d8 | ||
|
|
9f7d6655b2 | ||
|
|
f6a9253a53 | ||
|
|
48020e8901 | ||
|
|
5afa20d655 | ||
|
|
197baf066a | ||
|
|
c873647aae | ||
|
|
fc1847588d | ||
|
|
62b3d47b43 | ||
|
|
ddb9333ca6 | ||
|
|
830e0e4858 | ||
|
|
085c612f97 | ||
|
|
dea9fb2090 | ||
|
|
5813c903eb | ||
|
|
4f499e2c2e | ||
|
|
3884328251 | ||
|
|
7e9e2471ef | ||
|
|
2a433ee7fb | ||
|
|
118f835385 | ||
|
|
9a6a42bd44 | ||
|
|
9434149842 | ||
|
|
4107536c03 | ||
|
|
2aec782f58 | ||
|
|
eaaa943af3 | ||
|
|
964e8311a9 | ||
|
|
895dd12e4d | ||
|
|
d16036aa78 | ||
|
|
8b79a81eaf | ||
|
|
231e94dd2e | ||
|
|
f7be22893d | ||
|
|
3ac91a4bb6 | ||
|
|
43441a8d55 | ||
|
|
b02059f196 | ||
|
|
f107802f0e | ||
|
|
7faf1fe64b | ||
|
|
bf2fea64e7 | ||
|
|
de3eb3de9d | ||
|
|
55d080ac96 | ||
|
|
795bacb4fe | ||
|
|
f0ee12f92e | ||
|
|
0b14a2f869 | ||
|
|
2579ce65b9 | ||
|
|
77f86988cb | ||
|
|
b9b62dcd75 | ||
|
|
bfee7c55cd | ||
|
|
34efa87699 | ||
|
|
e839db7107 | ||
|
|
ffb16aa7bb | ||
|
|
a3b0b7675e | ||
|
|
602f62c17c | ||
|
|
ace7a1968d | ||
|
|
6efefe3780 | ||
|
|
c02f708d74 | ||
|
|
e7c47f523a | ||
|
|
5c8be152f4 | ||
|
|
4cdbfc84dd | ||
|
|
75ec856af3 | ||
|
|
88b5e16597 | ||
|
|
fff6fdd4eb | ||
|
|
c4ff544459 | ||
|
|
9c87942b43 | ||
|
|
0e4c9275b0 | ||
|
|
f11dce4269 | ||
|
|
f1340eb542 | ||
|
|
e89f5cbd8a | ||
|
|
975016700e | ||
|
|
a9017be606 | ||
|
|
d81d6831dc | ||
|
|
1b94373c7e | ||
|
|
d5e881ce1a | ||
|
|
a3f84aca0e | ||
|
|
0ab86b90f0 | ||
|
|
b6a606db35 | ||
|
|
578731f239 | ||
|
|
71db4ffe9c | ||
|
|
7421ec7b1a | ||
|
|
958712663e | ||
|
|
d96fa66487 | ||
|
|
36ac548842 | ||
|
|
fc44ab6128 | ||
|
|
f0da3936e5 | ||
|
|
04e694799d | ||
|
|
712cedb84f | ||
|
|
4fb3ddcf84 |
29
.travis.yml
29
.travis.yml
@@ -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
110
ChangeLog
@@ -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;
|
||||
|
||||
17
Makefile.am
17
Makefile.am
@@ -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
128
Makefile.in
vendored
@@ -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 */" ;\
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
107
configure
vendored
@@ -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'`\\"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
131
daemon/extension/CommandScript.cpp
Normal file
131
daemon/extension/CommandScript.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "nzbget.h"
|
||||
#include "CommandScript.h"
|
||||
#include "Options.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
static const int COMMANDPROCESS_SUCCESS = 93;
|
||||
static const int COMMANDPROCESS_ERROR = 94;
|
||||
|
||||
bool CommandScriptController::StartScript(const char* scriptName, const char* command,
|
||||
std::unique_ptr<Options::OptEntries> modifiedOptions)
|
||||
{
|
||||
CommandScriptController* scriptController = new CommandScriptController();
|
||||
scriptController->m_script = scriptName;
|
||||
scriptController->m_command = command;
|
||||
scriptController->m_logId = g_CommandScriptLog->Reset();
|
||||
scriptController->m_modifiedOptions = std::move(modifiedOptions);
|
||||
|
||||
scriptController->SetAutoDestroy(true);
|
||||
|
||||
scriptController->Start();
|
||||
|
||||
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
|
||||
{
|
||||
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandScriptController::Run()
|
||||
{
|
||||
ExecuteScriptList(m_script);
|
||||
}
|
||||
|
||||
void CommandScriptController::ExecuteScript(ScriptConfig::Script* script)
|
||||
{
|
||||
PrintMessage(Message::mkInfo, "Executing script %s with command %s", script->GetName(), *m_command);
|
||||
|
||||
SetArgs({script->GetLocation()});
|
||||
|
||||
BString<1024> infoName("script %s with command %s", script->GetName(), *m_command);
|
||||
SetInfoName(infoName);
|
||||
|
||||
SetLogPrefix(script->GetDisplayName());
|
||||
PrepareParams(script->GetName());
|
||||
|
||||
int exitCode = Execute();
|
||||
|
||||
infoName[0] = 'S'; // uppercase
|
||||
SetLogPrefix(nullptr);
|
||||
|
||||
switch (exitCode)
|
||||
{
|
||||
case COMMANDPROCESS_SUCCESS:
|
||||
PrintMessage(Message::mkInfo, "%s successful", *infoName);
|
||||
break;
|
||||
|
||||
case COMMANDPROCESS_ERROR:
|
||||
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
|
||||
PrintMessage(Message::mkError, "%s failed", *infoName);
|
||||
break;
|
||||
|
||||
default:
|
||||
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", *infoName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScriptController::PrepareParams(const char* scriptName)
|
||||
{
|
||||
ResetEnv();
|
||||
|
||||
SetEnvVar("NZBCP_COMMAND", m_command);
|
||||
|
||||
PrepareEnvScript(nullptr, scriptName);
|
||||
}
|
||||
|
||||
const char* CommandScriptController::GetOptValue(const char* name, const char* value)
|
||||
{
|
||||
Options::OptEntry* entry = m_modifiedOptions->FindOption(name);
|
||||
return entry ? entry->GetValue() : value;
|
||||
}
|
||||
|
||||
void CommandScriptController::AddMessage(Message::EKind kind, const char * text)
|
||||
{
|
||||
NzbScriptController::AddMessage(kind, text);
|
||||
g_CommandScriptLog->AddMessage(m_logId, kind, text);
|
||||
}
|
||||
|
||||
|
||||
int CommandScriptLog::Reset()
|
||||
{
|
||||
Guard guard(m_logMutex);
|
||||
m_messages.clear();
|
||||
return ++m_idScriptGen;
|
||||
}
|
||||
|
||||
void CommandScriptLog::AddMessage(int scriptId, Message::EKind kind, const char * text)
|
||||
{
|
||||
Guard guard(m_logMutex);
|
||||
|
||||
// save only messages from the last started script
|
||||
if (scriptId == m_idScriptGen)
|
||||
{
|
||||
m_messages.emplace_back(++m_idMessageGen, kind, Util::CurrentTime(), text);
|
||||
}
|
||||
}
|
||||
63
daemon/extension/CommandScript.h
Normal file
63
daemon/extension/CommandScript.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of nzbget. See <http://nzbget.net>.
|
||||
*
|
||||
* Copyright (C) 2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef COMMANDSCRIPT_H
|
||||
#define COMMANDSCRIPT_H
|
||||
|
||||
#include "NzbScript.h"
|
||||
#include "Log.h"
|
||||
|
||||
class CommandScriptController : public Thread, public NzbScriptController
|
||||
{
|
||||
public:
|
||||
virtual void Run();
|
||||
static bool StartScript(const char* scriptName, const char* command, std::unique_ptr<Options::OptEntries> modifiedOptions);
|
||||
|
||||
protected:
|
||||
virtual void ExecuteScript(ScriptConfig::Script* script);
|
||||
virtual void AddMessage(Message::EKind kind, const char* text);
|
||||
virtual const char* GetOptValue(const char* name, const char* value);
|
||||
|
||||
private:
|
||||
CString m_script;
|
||||
CString m_command;
|
||||
int m_logId;
|
||||
std::unique_ptr<Options::OptEntries> m_modifiedOptions;
|
||||
|
||||
void PrepareParams(const char* scriptName);
|
||||
};
|
||||
|
||||
class CommandScriptLog
|
||||
{
|
||||
public:
|
||||
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
|
||||
int Reset();
|
||||
void AddMessage(int scriptId, Message::EKind kind, const char* text);
|
||||
|
||||
private:
|
||||
MessageList m_messages;
|
||||
Mutex m_logMutex;
|
||||
int m_idMessageGen;
|
||||
int m_idScriptGen;
|
||||
};
|
||||
|
||||
extern CommandScriptLog* g_CommandScriptLog;
|
||||
|
||||
#endif
|
||||
@@ -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" };
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
558
daemon/postprocess/DirectUnpack.cpp
Normal file
558
daemon/postprocess/DirectUnpack.cpp
Normal 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(), ¶ms, "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);
|
||||
}
|
||||
86
daemon/postprocess/DirectUnpack.h
Normal file
86
daemon/postprocess/DirectUnpack.h
Normal 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
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
549
daemon/queue/DirectRenamer.cpp
Normal file
549
daemon/queue/DirectRenamer.cpp
Normal 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
|
||||
}
|
||||
84
daemon/queue/DirectRenamer.h
Normal file
84
daemon/queue/DirectRenamer.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
134
nzbget.conf
134
nzbget.conf
@@ -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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
28
tests/functional/download/download_filename_test.py
Normal file
28
tests/functional/download/download_filename_test.py
Normal 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')
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
17
tests/functional/rename/rename_opt1_test.py
Normal file
17
tests/functional/rename/rename_opt1_test.py
Normal 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'
|
||||
83
tests/functional/rename/rename_opt2_test.py
Normal file
83
tests/functional/rename/rename_opt2_test.py
Normal 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']
|
||||
83
tests/functional/rename/rename_opt3_test.py
Normal file
83
tests/functional/rename/rename_opt3_test.py
Normal 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']
|
||||
@@ -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++;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
143
tests/postprocess/DirectUnpackTest.cpp
Normal file
143
tests/postprocess/DirectUnpackTest.cpp
Normal 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()));
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
219
webui/config.js
219
webui/config.js
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
253
webui/edit.js
253
webui/edit.js
@@ -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 **************************************************/
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
288
webui/history.js
288
webui/history.js
@@ -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($)
|
||||
|
||||
200
webui/index.html
200
webui/index.html
@@ -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" />
|
||||
<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>
|
||||
|
||||
107
webui/index.js
107
webui/index.js
@@ -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));
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
138
webui/style.css
138
webui/style.css
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user